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