1 /* 2 * Copyright 2004-2008, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Mike Berg <mike@berg-net.us> 7 * Julun <host.haiku@gmx.de> 8 * Philippe Saint-Pierre <stpere@gmail.com> 9 */ 10 11 /* 12 Exceptions: 13 doesn't calc "Time in" time. 14 15 Issues: 16 After experimenting with both Time Prefs, it seems the original 17 doesn't use the link file in the users settings file to get the 18 current Timezone. Need to find the call it uses to get its 19 inital info so I can get exact duplication. 20 */ 21 22 #include "ZoneView.h" 23 #include "TimeMessages.h" 24 #include "TZDisplay.h" 25 #include "TimeWindow.h" 26 27 28 #include <Button.h> 29 #include <Directory.h> 30 #include <Entry.h> 31 #include <FindDirectory.h> 32 #include <ListItem.h> 33 #include <ListView.h> 34 #include <MenuField.h> 35 #include <MenuItem.h> 36 #include <MenuBar.h> 37 #include <PopUpMenu.h> 38 #include <ScrollView.h> 39 #include <StorageDefs.h> 40 #include <String.h> 41 #include <View.h> 42 #include <Window.h> 43 44 45 #include <stdio.h> 46 #include <stdlib.h> 47 48 49 class TZoneItem : public BStringItem { 50 public: 51 TZoneItem(const char *text, const char *zone) 52 : BStringItem(text), fZone(new BPath(zone)) { } 53 54 ~TZoneItem() { delete fZone; } 55 56 const char *Zone() const { return fZone->Leaf(); } 57 const char *Path() const { return fZone->Path(); } 58 59 private: 60 BPath *fZone; 61 }; 62 63 64 TimeZoneView::TimeZoneView(BRect frame) 65 : BView(frame, "timeZoneView", B_FOLLOW_NONE, B_WILL_DRAW | B_NAVIGABLE_JUMP), 66 fInitialized(false) 67 { 68 ReadTimeZoneLink(); 69 InitView(); 70 } 71 72 73 bool 74 TimeZoneView::CheckCanRevert() 75 { 76 return fCurrentZone != fOldZone; 77 } 78 79 80 void 81 TimeZoneView::_Revert() 82 { 83 BPath parent; 84 85 fCurrentZone = fOldZone; 86 int32 czone; 87 88 if (strcmp(fCurrentZone.Leaf(), "Greenwich") == 0) { 89 if (BMenuItem* item = fRegionPopUp->FindItem("Others")) 90 item->SetMarked(true); 91 czone = FillCityList("Others"); 92 } else { 93 fCurrentZone.GetParent(&parent); 94 if (BMenuItem* item = fRegionPopUp->FindItem(parent.Leaf())) 95 item->SetMarked(true); 96 czone = FillCityList(parent.Path()); 97 } 98 99 if (czone > -1) { 100 fCityList->Select(czone); 101 fCityList->ScrollToSelection(); 102 fCurrent->SetText(((TZoneItem *)fCityList->ItemAt(czone))->Text()); 103 SetPreview(); 104 } 105 SetTimeZone(); 106 } 107 108 109 TimeZoneView::~TimeZoneView() 110 { 111 } 112 113 114 void 115 TimeZoneView::AttachedToWindow() 116 { 117 if (Parent()) 118 SetViewColor(Parent()->ViewColor()); 119 120 if (!fInitialized) { 121 fInitialized = true; 122 123 fSetZone->SetTarget(this); 124 fCityList->SetTarget(this); 125 fRegionPopUp->SetTargetForItems(this); 126 127 // update displays 128 BPath parent; 129 if (strcmp(fCurrentZone.Leaf(), "Greenwich") != 0) { 130 fCurrentZone.GetParent(&parent); 131 int32 czone = FillCityList(parent.Path()); 132 if (czone > -1) { 133 fCityList->Select(czone); 134 fCurrent->SetText(((TZoneItem *)fCityList->ItemAt(czone))->Text()); 135 } 136 } else { 137 int32 czone = FillCityList("Others"); 138 if (czone > -1) { 139 fCityList->Select(czone); 140 fCurrent->SetText(((TZoneItem *)fCityList->ItemAt(czone))->Text()); 141 } 142 } 143 } 144 fCityList->ScrollToSelection(); 145 } 146 147 148 void 149 TimeZoneView::MessageReceived(BMessage *message) 150 { 151 int32 change; 152 switch(message->what) { 153 case B_OBSERVER_NOTICE_CHANGE: 154 message->FindInt32(B_OBSERVE_WHAT_CHANGE, &change); 155 switch(change) { 156 case H_TM_CHANGED: 157 UpdateDateTime(message); 158 break; 159 160 default: 161 BView::MessageReceived(message); 162 break; 163 } 164 break; 165 case H_REGION_CHANGED: 166 ChangeRegion(message); 167 break; 168 169 case H_SET_TIME_ZONE: 170 { 171 SetTimeZone(); 172 BMessage msg(*message); 173 msg.what = kRTCUpdate; 174 Window()->PostMessage(&msg); 175 ((TTimeWindow*)Window())->SetRevertStatus(); 176 break; 177 } 178 179 case kMsgRevert: 180 _Revert(); 181 break; 182 183 case H_CITY_CHANGED: 184 SetPreview(); 185 break; 186 187 default: 188 BView::MessageReceived(message); 189 break; 190 } 191 } 192 193 194 void 195 TimeZoneView::UpdateDateTime(BMessage *message) 196 { 197 int32 hour; 198 int32 minute; 199 200 // only need hour and minute 201 if (message->FindInt32("hour", &hour) == B_OK 202 && message->FindInt32("minute", &minute) == B_OK) { 203 if (fHour != hour || fMinute != minute) { 204 fHour = hour; 205 fMinute = minute; 206 fCurrent->SetTime(hour, minute); 207 208 // do calc to get other zone time 209 if (fCityList->CurrentSelection() > -1) 210 SetPreview(); 211 } 212 } 213 } 214 215 216 void 217 TimeZoneView::InitView() 218 { 219 // Zone menu 220 fRegionPopUp = new BPopUpMenu("", true, true, B_ITEMS_IN_COLUMN); 221 222 BuildRegionMenu(); 223 224 // left side 225 BRect frameLeft(Bounds()); 226 frameLeft.right = frameLeft.Width() / 2.0; 227 frameLeft.InsetBy(10.0f, 10.0f); 228 229 BMenuField *menuField = new BMenuField(frameLeft, "regions", NULL, fRegionPopUp, false); 230 AddChild(menuField); 231 menuField->ResizeToPreferred(); 232 233 frameLeft.top = menuField->Frame().bottom + 10.0; 234 frameLeft.right -= B_V_SCROLL_BAR_WIDTH; 235 236 // City Listing 237 fCityList = new BListView(frameLeft, "cityList", B_SINGLE_SELECTION_LIST); 238 fCityList->SetSelectionMessage(new BMessage(H_CITY_CHANGED)); 239 fCityList->SetInvocationMessage(new BMessage(H_SET_TIME_ZONE)); 240 241 BScrollView *scrollList = new BScrollView("scrollList", fCityList, 242 B_FOLLOW_ALL, 0, false, true); 243 AddChild(scrollList); 244 245 // right side 246 BRect frameRight(Bounds()); 247 frameRight.left = frameRight.Width() / 2.0; 248 frameRight.InsetBy(10.0f, 10.0f); 249 frameRight.top = frameLeft.top; 250 251 // Time Displays 252 fCurrent = new TTZDisplay(frameRight, "currentTime", "Current time:"); 253 AddChild(fCurrent); 254 fCurrent->ResizeToPreferred(); 255 256 frameRight.top = fCurrent->Frame().bottom + 10.0; 257 fPreview = new TTZDisplay(frameRight, "previewTime", "Preview time:"); 258 AddChild(fPreview); 259 fPreview->ResizeToPreferred(); 260 261 // set button 262 fSetZone = new BButton(frameRight, "setTimeZone", "Set Time Zone", 263 new BMessage(H_SET_TIME_ZONE)); 264 AddChild(fSetZone); 265 fSetZone->SetEnabled(false); 266 fSetZone->ResizeToPreferred(); 267 268 fSetZone->MoveTo(frameRight.right - fSetZone->Bounds().Width(), 269 scrollList->Frame().bottom - fSetZone->Bounds().Height()); 270 } 271 272 273 void 274 TimeZoneView::BuildRegionMenu() 275 { 276 BPath path; 277 if (find_directory(B_BEOS_ETC_DIRECTORY, &path) != B_OK) 278 return; 279 280 path.Append("timezones"); 281 282 // get current region 283 BPath region; 284 fCurrentZone.GetParent(®ion); 285 286 bool markit; 287 BEntry entry; 288 BMenuItem *item; 289 BDirectory dir(path.Path()); 290 291 dir.Rewind(); 292 293 // walk timezones dir and add entries to menu 294 BString itemtext; 295 while (dir.GetNextEntry(&entry) == B_NO_ERROR) { 296 if (entry.IsDirectory()) { 297 path.SetTo(&entry); 298 itemtext = path.Leaf(); 299 300 // skip Etc directory 301 if (itemtext.Compare("Etc", 3) == 0) 302 continue; 303 304 markit = itemtext.Compare(region.Leaf()) == 0; 305 306 // add Ocean to oceans :) 307 if (itemtext.Compare("Atlantic", 8) == 0 308 ||itemtext.Compare("Pacific", 7) == 0 309 ||itemtext.Compare("Indian", 6) == 0) 310 itemtext.Append(" Ocean"); 311 312 // underscores are spaces 313 itemtext = itemtext.ReplaceAll('_', ' '); 314 315 BMessage *msg = new BMessage(H_REGION_CHANGED); 316 msg->AddString("region", path.Path()); 317 318 item = new BMenuItem(itemtext.String(), msg); 319 item->SetMarked(markit); 320 fRegionPopUp->AddItem(item); 321 } 322 } 323 BMessage *msg = new BMessage(H_REGION_CHANGED); 324 msg->AddString("region", "Others"); 325 326 item = new BMenuItem("Others", msg); 327 item->SetMarked(strcmp(fCurrentZone.Leaf(), "Greenwich") == 0); 328 fRegionPopUp->AddItem(item); 329 } 330 331 332 int32 333 TimeZoneView::FillCityList(const char *area) 334 { 335 // clear list 336 int32 count = fCityList->CountItems(); 337 if (count > 0) { 338 for (int32 idx = count; idx >= 0; idx--) 339 delete fCityList->RemoveItem(idx); 340 fCityList->MakeEmpty(); 341 } 342 343 BStringItem *city; 344 int32 index = -1; 345 if (strcmp(area, "Others") != 0) { 346 347 // Enter time zones directory. Find subdir with name that matches string 348 // stored in area. Enter subdirectory and count the items. For each item, 349 // add a StringItem to fCityList Time zones directory 350 351 BPath path; 352 if (find_directory(B_BEOS_ETC_DIRECTORY, &path) != B_OK) 353 return 0; 354 355 path.Append("timezones"); 356 357 BPath Area(area); 358 BDirectory zoneDir(path.Path()); 359 BDirectory cityDir; 360 BString city_name; 361 BEntry entry; 362 363 364 //locate subdirectory: 365 if (zoneDir.Contains(Area.Leaf(), B_DIRECTORY_NODE)) { 366 cityDir.SetTo(&zoneDir, Area.Leaf()); 367 368 // There is a subdir with a name that matches 'area'. That's the one!! 369 // Iterate over the items in the subdir, fill listview with TZoneItems 370 while(cityDir.GetNextEntry(&entry) == B_NO_ERROR) { 371 if (!entry.IsDirectory()) { 372 BPath zone(&entry); 373 city_name = zone.Leaf(); 374 city_name.ReplaceAll("_IN", ", Indiana"); 375 city_name.ReplaceAll("__Calif", ", Calif"); 376 city_name.ReplaceAll("__", ", "); 377 city_name.ReplaceAll("_", " "); 378 city = new TZoneItem(city_name.String(), zone.Path()); 379 fCityList->AddItem(city); 380 if (strcmp(fCurrentZone.Leaf(), zone.Leaf()) == 0) 381 index = fCityList->IndexOf(city); 382 } 383 } 384 } 385 } else { 386 city = new TZoneItem("Greenwich", "/boot/beos/etc/timezones/Greenwich"); 387 fCityList->AddItem(city); 388 if (strcmp(fCurrentZone.Leaf(), "Greenwich") == 0) { 389 index = fCityList->IndexOf(city); 390 } 391 } 392 return index; 393 } 394 395 396 void 397 TimeZoneView::ChangeRegion(BMessage *message) 398 { 399 BString area; 400 message->FindString("region", &area); 401 402 FillCityList(area.String()); 403 } 404 405 406 void 407 TimeZoneView::ReadTimeZoneLink() 408 { 409 BEntry tzLink; 410 411 #if TARGET_PLATFORM_HAIKU 412 extern status_t _kern_get_tzfilename(char *filename, size_t length, bool *_isGMT); 413 414 char tzFileName[B_OS_PATH_LENGTH]; 415 bool isGMT; 416 _kern_get_tzfilename(tzFileName, B_OS_PATH_LENGTH, &isGMT); 417 tzLink.SetTo(tzFileName); 418 #else 419 /* reads the timezone symlink from B_USER_SETTINGS_DIRECTORY currently 420 this sets fCurrentZone to the symlink value, this is wrong. The 421 original can get users timezone without a timezone symlink present. 422 423 Defaults are set to different values to clue in on what error was returned 424 GMT is set when the link is invalid EST is set when the settings dir can't 425 be found what should never happen. 426 */ 427 428 429 BPath path; 430 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { 431 432 path.Append("timezone"); 433 tzLink.SetTo(path.Path(), true); 434 435 if (tzLink.InitCheck() != B_OK) {// link is broken 436 tzLink.SetTo("/boot/beos/etc/timezones/Pacific/fiji"); 437 // do something like set to a default GMT??? 438 } 439 else if (!tzLink.Exists()) { // link doesn't exists 440 tzLink.SetTo("/boot/beos/etc/timezones/Greenwich"); 441 } 442 } else { 443 // directory doesn't exist 444 tzLink.SetTo("/boot/beos/etc/timezones/EST"); 445 } 446 #endif 447 // we need something in the current zone 448 fCurrentZone.SetTo(&tzLink); 449 fOldZone.SetTo(&tzLink); 450 } 451 452 453 void 454 TimeZoneView::SetPreview() 455 { 456 int32 selection = fCityList->CurrentSelection(); 457 if (selection >= 0) { 458 TZoneItem *item = (TZoneItem *)fCityList->ItemAt(selection); 459 460 // set timezone to selection 461 SetTimeZone(item->Path()); 462 463 // calc preview time 464 time_t current = time(0); 465 struct tm *ltime = localtime(¤t); 466 467 // update prview 468 fPreview->SetText(item->Text()); 469 fPreview->SetTime(ltime->tm_hour, ltime->tm_min); 470 471 // set timezone back to current 472 SetTimeZone(fCurrentZone.Path()); 473 474 fSetZone->SetEnabled((strcmp(fCurrent->Text(), item->Text()) != 0)); 475 } 476 } 477 478 479 void 480 TimeZoneView::SetCurrent(const char *text) 481 { 482 SetTimeZone(fCurrentZone.Path()); 483 484 time_t current = time(0); 485 struct tm *ltime = localtime(¤t); 486 487 fCurrent->SetText(text); 488 fCurrent->SetTime(ltime->tm_hour, ltime->tm_min); 489 } 490 491 492 void 493 TimeZoneView::SetTimeZone() 494 { 495 /* set time based on supplied timezone. How to do this? 496 1) replace symlink "timezone" in B_USER_SETTINGS_DIR with a link to the new timezone 497 2) set TZ environment var 498 3) call settz() 499 4) call set_timezone from OS.h passing path to timezone file 500 */ 501 502 // get path to current link 503 // update/create timezone symlink in B_USER_SETTINGS_DIRECTORY 504 BPath path; 505 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 506 return; 507 508 path.Append("timezone"); 509 510 // build target for new link 511 int32 selection = fCityList->CurrentSelection(); 512 if (selection < 0) 513 return; 514 515 BPath target(((TZoneItem *)fCityList->ItemAt(selection))->Path()); 516 517 // remove old 518 BEntry entry(path.Path()); 519 if (entry.Exists()) 520 entry.Remove(); 521 522 // create new 523 BDirectory dir(target.Path()); 524 if (dir.CreateSymLink(path.Path(), target.Path(), NULL) != B_OK) 525 fprintf(stderr, "timezone not linked\n"); 526 527 // update environment 528 SetTimeZone(target.Path()); 529 530 // update display 531 time_t current = time(0); 532 struct tm *ltime = localtime(¤t); 533 534 char tza[B_PATH_NAME_LENGTH]; 535 sprintf(tza, "%s", target.Path()); 536 set_timezone(tza); 537 538 // disable button 539 fSetZone->SetEnabled(false); 540 541 time_t newtime = mktime(ltime); 542 ltime = localtime(&newtime); 543 stime(&newtime); 544 545 fHour = ltime->tm_hour; 546 fMinute = ltime->tm_min; 547 fCurrentZone.SetTo(target.Path()); 548 SetCurrent(((TZoneItem *)fCityList->ItemAt(selection))->Text()); 549 } 550 551 552 void 553 TimeZoneView::SetTimeZone(const char *zone) 554 { 555 putenv(BString("TZ=").Append(zone).String()); 556 tzset(); 557 } 558 559