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