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