xref: /haiku/src/preferences/time/ZoneView.cpp (revision 90ca02568835b140b0e59de496a7f1f1d3513f67)
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(&region);
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(&current);
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(&current);
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(&current);
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