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