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 BMessage msg(*message); 172 msg.what = kRTCUpdate; 173 Window()->PostMessage(&msg); 174 ((TTimeWindow*)Window())->SetRevertStatus(); 175 break; 176 } 177 178 case kMsgRevert: 179 _Revert(); 180 break; 181 182 case H_CITY_CHANGED: 183 SetPreview(); 184 break; 185 186 default: 187 BView::MessageReceived(message); 188 break; 189 } 190 } 191 192 193 void 194 TimeZoneView::UpdateDateTime(BMessage *message) 195 { 196 int32 hour; 197 int32 minute; 198 199 // only need hour and minute 200 if (message->FindInt32("hour", &hour) == B_OK 201 && message->FindInt32("minute", &minute) == B_OK) { 202 if (fHour != hour || fMinute != minute) { 203 fHour = hour; 204 fMinute = minute; 205 fCurrent->SetTime(hour, minute); 206 207 // do calc to get other zone time 208 if (fCityList->CurrentSelection() > -1) 209 SetPreview(); 210 } 211 } 212 } 213 214 215 void 216 TimeZoneView::InitView() 217 { 218 // Zone menu 219 fRegionPopUp = new BPopUpMenu("", true, true, B_ITEMS_IN_COLUMN); 220 221 BuildRegionMenu(); 222 223 // left side 224 BRect frameLeft(Bounds()); 225 frameLeft.right = frameLeft.Width() / 2.0; 226 frameLeft.InsetBy(10.0f, 10.0f); 227 228 BMenuField *menuField = new BMenuField(frameLeft, "regions", NULL, fRegionPopUp, false); 229 AddChild(menuField); 230 menuField->ResizeToPreferred(); 231 232 frameLeft.top = menuField->Frame().bottom + 10.0; 233 frameLeft.right -= B_V_SCROLL_BAR_WIDTH; 234 235 // City Listing 236 fCityList = new BListView(frameLeft, "cityList", B_SINGLE_SELECTION_LIST); 237 fCityList->SetSelectionMessage(new BMessage(H_CITY_CHANGED)); 238 fCityList->SetInvocationMessage(new BMessage(H_SET_TIME_ZONE)); 239 240 BScrollView *scrollList = new BScrollView("scrollList", fCityList, 241 B_FOLLOW_ALL, 0, false, true); 242 AddChild(scrollList); 243 244 // right side 245 BRect frameRight(Bounds()); 246 frameRight.left = frameRight.Width() / 2.0; 247 frameRight.InsetBy(10.0f, 10.0f); 248 frameRight.top = frameLeft.top; 249 250 // Time Displays 251 fCurrent = new TTZDisplay(frameRight, "currentTime", "Current time:"); 252 AddChild(fCurrent); 253 fCurrent->ResizeToPreferred(); 254 255 frameRight.top = fCurrent->Frame().bottom + 10.0; 256 fPreview = new TTZDisplay(frameRight, "previewTime", "Preview time:"); 257 AddChild(fPreview); 258 fPreview->ResizeToPreferred(); 259 260 // set button 261 fSetZone = new BButton(frameRight, "setTimeZone", "Set Time Zone", 262 new BMessage(H_SET_TIME_ZONE)); 263 AddChild(fSetZone); 264 fSetZone->SetEnabled(false); 265 fSetZone->ResizeToPreferred(); 266 267 fSetZone->MoveTo(frameRight.right - fSetZone->Bounds().Width(), 268 scrollList->Frame().bottom - fSetZone->Bounds().Height()); 269 } 270 271 272 void 273 TimeZoneView::BuildRegionMenu() 274 { 275 BPath path; 276 if (_GetTimezonesPath(path) != B_OK) 277 return; 278 279 // get current region 280 BPath region; 281 fCurrentZone.GetParent(®ion); 282 283 bool markit; 284 BEntry entry; 285 BMenuItem *item; 286 BDirectory dir(path.Path()); 287 288 dir.Rewind(); 289 290 // walk timezones dir and add entries to menu 291 BString itemtext; 292 while (dir.GetNextEntry(&entry) == B_NO_ERROR) { 293 if (entry.IsDirectory()) { 294 path.SetTo(&entry); 295 itemtext = path.Leaf(); 296 297 // skip Etc directory 298 if (itemtext.Compare("Etc", 3) == 0) 299 continue; 300 301 markit = itemtext.Compare(region.Leaf()) == 0; 302 303 // add Ocean to oceans :) 304 if (itemtext.Compare("Atlantic", 8) == 0 305 ||itemtext.Compare("Pacific", 7) == 0 306 ||itemtext.Compare("Indian", 6) == 0) 307 itemtext.Append(" Ocean"); 308 309 // underscores are spaces 310 itemtext = itemtext.ReplaceAll('_', ' '); 311 312 BMessage *msg = new BMessage(H_REGION_CHANGED); 313 msg->AddString("region", path.Path()); 314 315 item = new BMenuItem(itemtext.String(), msg); 316 item->SetMarked(markit); 317 fRegionPopUp->AddItem(item); 318 } 319 } 320 BMessage *msg = new BMessage(H_REGION_CHANGED); 321 msg->AddString("region", "Others"); 322 323 item = new BMenuItem("Others", msg); 324 if (fCurrentZone.Leaf() != NULL) 325 item->SetMarked(strcmp(fCurrentZone.Leaf(), "Greenwich") == 0); 326 fRegionPopUp->AddItem(item); 327 } 328 329 330 int32 331 TimeZoneView::FillCityList(const char *area) 332 { 333 // clear list 334 int32 count = fCityList->CountItems(); 335 if (count > 0) { 336 for (int32 idx = count; idx >= 0; idx--) 337 delete fCityList->RemoveItem(idx); 338 fCityList->MakeEmpty(); 339 } 340 341 BStringItem *city; 342 int32 index = -1; 343 if (strcmp(area, "Others") != 0) { 344 // Enter time zones directory. Find subdir with name that matches string 345 // stored in area. Enter subdirectory and count the items. For each item, 346 // add a StringItem to fCityList Time zones directory 347 348 BPath path; 349 _GetTimezonesPath(path); 350 351 BPath areaPath(area); 352 BDirectory zoneDir(path.Path()); 353 BDirectory cityDir; 354 BString city_name; 355 BEntry entry; 356 357 // locate subdirectory: 358 if (zoneDir.Contains(areaPath.Leaf(), B_DIRECTORY_NODE)) { 359 cityDir.SetTo(&zoneDir, areaPath.Leaf()); 360 361 // There is a subdir with a name that matches 'area'. That's the one!! 362 // Iterate over the items in the subdir, fill listview with TZoneItems 363 while(cityDir.GetNextEntry(&entry) == B_NO_ERROR) { 364 if (!entry.IsDirectory()) { 365 BPath zone(&entry); 366 city_name = zone.Leaf(); 367 city_name.ReplaceAll("_IN", ", Indiana"); 368 city_name.ReplaceAll("__Calif", ", Calif"); 369 city_name.ReplaceAll("__", ", "); 370 city_name.ReplaceAll("_", " "); 371 city = new TZoneItem(city_name.String(), zone.Path()); 372 fCityList->AddItem(city); 373 if (fCurrentZone.Leaf() != NULL 374 && strcmp(fCurrentZone.Leaf(), zone.Leaf()) == 0) 375 index = fCityList->IndexOf(city); 376 } 377 } 378 } 379 } else { 380 BPath path; 381 _GetTimezonesPath(path); 382 path.Append("Greenwich"); 383 384 city = new TZoneItem("Greenwich", path.Path()); 385 fCityList->AddItem(city); 386 if (fCurrentZone.Leaf() != NULL 387 && strcmp(fCurrentZone.Leaf(), "Greenwich") == 0) { 388 index = fCityList->IndexOf(city); 389 } 390 } 391 return index; 392 } 393 394 395 void 396 TimeZoneView::ChangeRegion(BMessage *message) 397 { 398 BString area; 399 message->FindString("region", &area); 400 401 FillCityList(area.String()); 402 } 403 404 405 void 406 TimeZoneView::ReadTimeZoneLink() 407 { 408 char tzFileName[B_PATH_NAME_LENGTH]; 409 bool isGMT; 410 _kern_get_tzfilename(tzFileName, B_PATH_NAME_LENGTH, &isGMT); 411 412 BEntry tzLink; 413 tzLink.SetTo(tzFileName); 414 415 if (!tzLink.Exists()) { 416 BPath path; 417 _GetTimezonesPath(path); 418 path.Append("Greenwich"); 419 420 tzLink.SetTo(path.Path()); 421 } 422 423 // we need something in the current zone 424 fCurrentZone.SetTo(&tzLink); 425 fOldZone.SetTo(&tzLink); 426 } 427 428 429 void 430 TimeZoneView::SetPreview() 431 { 432 int32 selection = fCityList->CurrentSelection(); 433 if (selection >= 0) { 434 TZoneItem *item = (TZoneItem *)fCityList->ItemAt(selection); 435 436 // set timezone to selection 437 SetTimeZone(item->Path()); 438 439 // calc preview time 440 time_t current = time(0); 441 struct tm *ltime = localtime(¤t); 442 443 // update prview 444 fPreview->SetText(item->Text()); 445 fPreview->SetTime(ltime->tm_hour, ltime->tm_min); 446 447 // set timezone back to current 448 SetTimeZone(fCurrentZone.Path()); 449 450 fSetZone->SetEnabled((strcmp(fCurrent->Text(), item->Text()) != 0)); 451 } 452 } 453 454 455 void 456 TimeZoneView::SetCurrent(const char *text) 457 { 458 SetTimeZone(fCurrentZone.Path()); 459 460 time_t current = time(0); 461 struct tm *ltime = localtime(¤t); 462 463 fCurrent->SetText(text); 464 fCurrent->SetTime(ltime->tm_hour, ltime->tm_min); 465 } 466 467 468 void 469 TimeZoneView::SetTimeZone() 470 { 471 /* set time based on supplied timezone. How to do this? 472 1) replace symlink "timezone" in B_USER_SETTINGS_DIR with a link to the new timezone 473 2) set TZ environment var 474 3) call settz() 475 4) call set_timezone from OS.h passing path to timezone file 476 */ 477 478 // get path to current link 479 // update/create timezone symlink in B_USER_SETTINGS_DIRECTORY 480 BPath path; 481 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 482 return; 483 484 path.Append("timezone"); 485 486 // build target for new link 487 int32 selection = fCityList->CurrentSelection(); 488 if (selection < 0) 489 return; 490 491 BPath target(((TZoneItem *)fCityList->ItemAt(selection))->Path()); 492 493 // remove old 494 BEntry entry(path.Path()); 495 if (entry.Exists()) 496 entry.Remove(); 497 498 // create new 499 BDirectory dir(target.Path()); 500 if (dir.CreateSymLink(path.Path(), target.Path(), NULL) != B_OK) 501 fprintf(stderr, "timezone not linked\n"); 502 503 // update environment 504 SetTimeZone(target.Path()); 505 506 // update display 507 time_t current = time(0); 508 struct tm *ltime = localtime(¤t); 509 510 char tza[B_PATH_NAME_LENGTH]; 511 sprintf(tza, "%s", target.Path()); 512 set_timezone(tza); 513 514 // disable button 515 fSetZone->SetEnabled(false); 516 517 time_t newtime = mktime(ltime); 518 ltime = localtime(&newtime); 519 stime(&newtime); 520 521 fHour = ltime->tm_hour; 522 fMinute = ltime->tm_min; 523 fCurrentZone.SetTo(target.Path()); 524 SetCurrent(((TZoneItem *)fCityList->ItemAt(selection))->Text()); 525 } 526 527 528 void 529 TimeZoneView::SetTimeZone(const char *zone) 530 { 531 putenv(BString("TZ=").Append(zone).String()); 532 tzset(); 533 } 534 535 536 status_t 537 TimeZoneView::_GetTimezonesPath(BPath& path) 538 { 539 status_t status = find_directory(B_SYSTEM_DATA_DIRECTORY, &path); 540 if (status != B_OK) 541 return status; 542 543 return path.Append("timezones"); 544 } 545 546