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