xref: /haiku/src/apps/deskbar/TimeView.cpp (revision 93aeb8c3bc3f13cb1f282e3e749258a23790d947)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 
36 #include "TimeView.h"
37 
38 #ifdef _SHOW_CALENDAR_MENU_ITEM
39 #	include "CalendarMenuItem.h"
40 #endif
41 
42 #include <Debug.h>
43 #include <MenuItem.h>
44 #include <PopUpMenu.h>
45 #include <Roster.h>
46 #include <Window.h>
47 #include <Screen.h>
48 
49 #include <string.h>
50 
51 
52 static const char * const kMinString = "99:99 AM";
53 
54 
55 static float
56 FontHeight(BView *target, bool full)
57 {
58 	font_height fontInfo;
59 	target->GetFontHeight(&fontInfo);
60 	float h = fontInfo.ascent + fontInfo.descent;
61 
62 	if (full)
63 		h += fontInfo.leading;
64 
65 	return h;
66 }
67 
68 
69 enum {
70 	kMsgShowClock,
71 	kMsgChangeClock,
72 	kMsgHide
73 };
74 
75 
76 TTimeView::TTimeView(bool showSeconds, bool milTime, bool fullDate, bool euroDate, bool)
77 	: 	BView(BRect(-100,-100,-90,-90), "_deskbar_tv_",
78 	B_FOLLOW_RIGHT | B_FOLLOW_TOP,
79 	B_WILL_DRAW | B_PULSE_NEEDED | B_FRAME_EVENTS),
80 	fParent(NULL),
81 	fShowInterval(true), // ToDo: defaulting this to true until UI is in place
82 	fShowSeconds(showSeconds),
83 	fMilTime(milTime),
84 	fFullDate(fullDate),
85 	fCanShowFullDate(false),
86 	fEuroDate(euroDate),
87 	fOrientation(false)
88 {
89 	fShowingDate = false;
90 	fTime = fLastTime = time(NULL);
91 	fSeconds = fMinute = fHour = 0;
92 	fLastTimeStr[0] = 0;
93 	fLastDateStr[0] = 0;
94 	fNeedToUpdate = true;
95 }
96 
97 
98 #ifdef AS_REPLICANT
99 TTimeView::TTimeView(BMessage *data)
100 	: BView(data)
101 {
102 	fTime = fLastTime = time(NULL);
103 	data->FindBool("seconds", &fShowSeconds);
104 	data->FindBool("miltime", &fMilTime);
105 	data->FindBool("fulldate", &fFullDate);
106 	data->FindBool("eurodate", &fEuroDate);
107 	data->FindBool("interval", &fInterval);
108 	fShowingDate = false;
109 }
110 #endif
111 
112 
113 TTimeView::~TTimeView()
114 {
115 }
116 
117 
118 #ifdef AS_REPLICANT
119 BArchivable*
120 TTimeView::Instantiate(BMessage *data)
121 {
122 	if (!validate_instantiation(data, "TTimeView"))
123 		return NULL;
124 
125 	return new TTimeView(data);
126 }
127 
128 
129 status_t
130 TTimeView::Archive(BMessage *data, bool deep) const
131 {
132 	BView::Archive(data, deep);
133 	data->AddBool("seconds", fShowSeconds);
134 	data->AddBool("miltime", fMilTime);
135 	data->AddBool("fulldate", fFullDate);
136 	data->AddBool("eurodate", fEuroDate);
137 	data->AddBool("interval", fInterval);
138 	data->AddInt32("deskbar:private_align", B_ALIGN_RIGHT);
139 
140 	return B_OK;
141 }
142 #endif
143 
144 
145 void
146 TTimeView::AttachedToWindow()
147 {
148 	fTime = time(NULL);
149 
150 	SetFont(be_plain_font);
151 	if (Parent()) {
152 		fParent = Parent();
153 		SetViewColor(Parent()->ViewColor());
154 	} else
155 		SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
156 
157 	fFontHeight = FontHeight(this, true);
158 	ResizeToPreferred();
159 	CalculateTextPlacement();
160 }
161 
162 
163 void
164 TTimeView::GetPreferredSize(float *width, float *height)
165 {
166 	*height = fFontHeight;
167 
168 	GetCurrentTime();
169 	GetCurrentDate();
170 
171 	if (ShowingDate())
172 		*width = 6 + StringWidth(fDateStr);
173 	else {
174 		*width = 6 + StringWidth(fTimeStr);
175 		// Changed this from 10 to 6 so even with interval + seconds, there still
176 		// is room for two replicants in the default tray.
177 	}
178 }
179 
180 
181 void
182 TTimeView::ResizeToPreferred()
183 {
184 	float width, height;
185 	float oldWidth = Bounds().Width(), oldHeight = Bounds().Height();
186 
187 	GetPreferredSize(&width, &height);
188 	if (height != oldHeight || width != oldWidth) {
189 		ResizeTo(width, height);
190 		MoveBy(oldWidth - width, 0);
191 		fNeedToUpdate = true;
192 	}
193 }
194 
195 
196 void
197 TTimeView::FrameMoved(BPoint)
198 {
199 	Update();
200 }
201 
202 
203 void
204 TTimeView::MessageReceived(BMessage* message)
205 {
206 	switch (message->what) {
207 		case kMsgFullDate:
208 			ShowFullDate(!ShowingFullDate());
209 			break;
210 
211 		case kMsgShowSeconds:
212 			ShowSeconds(!ShowingSeconds());
213 			break;
214 
215 		case kMsgMilTime:
216 			ShowMilTime(!ShowingMilTime());
217 			break;
218 
219 		case kMsgEuroDate:
220 			ShowEuroDate(!ShowingEuroDate());
221 			break;
222 
223 		case kMsgChangeClock:
224 			// launch the time prefs app
225 			be_roster->Launch("application/x-vnd.Be-TIME");
226 			break;
227 
228 		case 'time':
229 			Window()->PostMessage(message, Parent());
230 			break;
231 
232 		default:
233 			BView::MessageReceived(message);
234 	}
235 }
236 
237 
238 void
239 TTimeView::GetCurrentTime()
240 {
241 	char tmp[64];
242 	tm time = *localtime(&fTime);
243 
244 	if (fMilTime) {
245 		strftime(tmp, 64, fShowSeconds ? "%H:%M:%S" : "%H:%M", &time);
246 	} else {
247 		if (fShowInterval)
248 			strftime(tmp, 64, fShowSeconds ? "%I:%M:%S %p" : "%I:%M %p", &time);
249 		else
250 			strftime(tmp, 64, fShowSeconds ? "%I:%M:%S" : "%I:%M", &time);
251 	}
252 
253 	//	remove leading 0 from time when hour is less than 10
254 	const char *str = tmp;
255 	if (str[0] == '0')
256 		str++;
257 
258 	strcpy(fTimeStr, str);
259 
260 	fSeconds = time.tm_sec;
261 	fMinute = time.tm_min;
262 	fHour = time.tm_hour;
263 }
264 
265 
266 const char *kShortDateFormat = "%m/%d/%y";
267 const char *kShortEuroDateFormat = "%d/%m/%y";
268 const char *kLongDateFormat = "%a, %B %d, %Y";
269 const char *kLongEuroDateFormat = "%a, %d %B, %Y";
270 
271 
272 void
273 TTimeView::GetCurrentDate()
274 {
275 	char tmp[64];
276 	tm time = *localtime(&fTime);
277 
278 	if (fFullDate && CanShowFullDate())
279 		strftime(tmp, 64, fEuroDate ? kLongEuroDateFormat : kLongDateFormat, &time);
280 	else
281 		strftime(tmp, 64, fEuroDate ? kShortEuroDateFormat : kShortDateFormat, &time);
282 
283 	//	remove leading 0 from date when month is less than 10 (MM/DD/YY)
284 	//  or remove leading 0 from date when day is less than 10 (DD/MM/YY)
285 	const char* str = tmp;
286 	if (str[0] == '0')
287 		str++;
288 
289 	strcpy(fDateStr, str);
290 }
291 
292 
293 void
294 TTimeView::Draw(BRect /*updateRect*/)
295 {
296 	PushState();
297 
298 	SetHighColor(ViewColor());
299 	SetLowColor(ViewColor());
300 	FillRect(Bounds());
301 	SetHighColor(0, 0, 0, 255);
302 
303 	if (fShowingDate) {
304 		MovePenTo(fDateLocation);
305 		DrawString(fDateStr);
306 	} else {
307 		MovePenTo(fTimeLocation);
308 		DrawString(fTimeStr);
309 	}
310 
311 	PopState();
312 }
313 
314 
315 void
316 TTimeView::MouseDown(BPoint point)
317 {
318 	uint32 buttons;
319 
320 	Window()->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons);
321 	if (buttons == B_SECONDARY_MOUSE_BUTTON) {
322 		ShowClockOptions(ConvertToScreen(point));
323 		return;
324 	}
325 
326 	//	flip to/from showing date or time
327 	fShowingDate = !fShowingDate;
328 	if (fShowingDate)
329 		fLastTime = time(NULL);
330 
331 	// invalidate last time/date strings and call the pulse
332 	// method directly to change the display instantly
333 	fLastDateStr[0] = '\0';
334 	fLastTimeStr[0] = '\0';
335 	Pulse();
336 
337 #ifdef _SHOW_CALENDAR_MENU_ITEM
338 	if (!fShowingDate)
339 		return;
340 
341 	// see if the user holds down the button long enough to show him the calendar
342 
343 	bigtime_t startTime = system_time();
344 
345 	// use the doubleClickSpeed as a treshold
346 	bigtime_t doubleClickSpeed;
347 	get_click_speed(&doubleClickSpeed);
348 
349 	while (buttons) {
350 		BPoint where;
351 		GetMouse(&where, &buttons, false);
352 
353 		if ((system_time() - startTime) > doubleClickSpeed) {
354 			BPopUpMenu *menu = new BPopUpMenu("", false, false);
355 			menu->SetFont(be_plain_font);
356 
357 			menu->AddItem(new CalendarMenuItem());
358 			menu->ResizeToPreferred();
359 
360 			point = where;
361 			BScreen screen;
362 			where.y = Bounds().bottom + 4;
363 
364 			// make sure the menu is visible at doesn't hide the date
365 			ConvertToScreen(&where);
366 			if (where.y + menu->Bounds().Height() > screen.Frame().bottom)
367 				where.y -= menu->Bounds().Height() + 2 * Bounds().Height();
368 
369 			ConvertToScreen(&point);
370 			menu->Go(where, true, true, BRect(point.x - 4, point.y - 4,
371 				point.x + 4, point.y + 4), true);
372 			return;
373 		}
374 
375 		snooze(15000);
376 	}
377 #endif
378 }
379 
380 
381 void
382 TTimeView::Pulse()
383 {
384 	time_t curTime = time(NULL);
385 	tm	ct = *localtime(&curTime);
386 	fTime = curTime;
387 
388 	GetCurrentTime();
389 	GetCurrentDate();
390 	if ((!fShowingDate && strcmp(fTimeStr, fLastTimeStr) != 0)
391 		|| 	(fShowingDate && strcmp(fDateStr, fLastDateStr) != 0)) {
392 		// Update bounds when the size of the strings has changed
393 		// For dates, Update() could be called two times in a row,
394 		// but that should only happen very rarely
395 		if ((!fShowingDate && fLastTimeStr[1] != fTimeStr[1]
396 				&& (fLastTimeStr[1] == ':' || fTimeStr[1] == ':'))
397 			|| (fShowingDate && strlen(fDateStr) != strlen(fLastDateStr))
398 			|| !fLastTimeStr[0])
399 			Update();
400 
401 		strcpy(fLastTimeStr, fTimeStr);
402 		strcpy(fLastDateStr, fDateStr);
403 		fNeedToUpdate = true;
404 	}
405 
406 	if (fShowingDate && (fLastTime + 5 <= time(NULL))) {
407 		fShowingDate = false;
408 		Update();	// Needs to happen since size can change here
409 	}
410 
411 	if (fNeedToUpdate) {
412 		fSeconds = ct.tm_sec;
413 		fMinute = ct.tm_min;
414 		fHour = ct.tm_hour;
415 		fInterval = ct.tm_hour >= 12;
416 
417 		Draw(Bounds());
418 		fNeedToUpdate = false;
419 	}
420 }
421 
422 
423 void
424 TTimeView::ShowSeconds(bool on)
425 {
426 	fShowSeconds = on;
427 	Update();
428 }
429 
430 
431 void
432 TTimeView::ShowMilTime(bool on)
433 {
434 	fMilTime = on;
435 	Update();
436 }
437 
438 
439 void
440 TTimeView::ShowDate(bool on)
441 {
442 	fShowingDate = on;
443 	Update();
444 }
445 
446 
447 void
448 TTimeView::ShowFullDate(bool on)
449 {
450 	fFullDate = on;
451 	Update();
452 }
453 
454 
455 void
456 TTimeView::ShowEuroDate(bool on)
457 {
458 	fEuroDate = on;
459 	Update();
460 }
461 
462 
463 void
464 TTimeView::AllowFullDate(bool allow)
465 {
466 	fCanShowFullDate = allow;
467 
468 	if (allow != ShowingFullDate())
469 		Update();
470 }
471 
472 
473 void
474 TTimeView::Update()
475 {
476 	GetCurrentTime();
477 	GetCurrentDate();
478 
479 	ResizeToPreferred();
480 	CalculateTextPlacement();
481 
482 	if (fParent) {
483 		fParent->MessageReceived(new BMessage('trfm'));
484 			// time string format realign
485 		fParent->Invalidate();
486 	}
487 }
488 
489 
490 void
491 TTimeView::SetOrientation(bool o)
492 {
493 	fOrientation = o;
494 	CalculateTextPlacement();
495 	Invalidate();
496 }
497 
498 
499 void
500 TTimeView::CalculateTextPlacement()
501 {
502 	BRect bounds(Bounds());
503 
504 	if (fOrientation) {		// vertical mode
505 		fDateLocation.x = bounds.Width()/2 - StringWidth(fDateStr)/2;
506 		fTimeLocation.x = bounds.Width()/2 - StringWidth(fTimeStr)/2;
507 	} else {
508 		fTimeLocation.x = bounds.Width() - StringWidth(fTimeStr) - 5;
509 		fDateLocation.x = bounds.Width() - StringWidth(fDateStr) - 5;
510 	}
511 	//	center vertically
512 	fDateLocation.y = fTimeLocation.y = bounds.Height()/2 + fFontHeight/2;
513 }
514 
515 
516 void
517 TTimeView::ShowClockOptions(BPoint point)
518 {
519 	BPopUpMenu *menu = new BPopUpMenu("", false, false);
520 	menu->SetFont(be_plain_font);
521 	BMenuItem *item;
522 
523 	item = new BMenuItem("Change Time" B_UTF8_ELLIPSIS, new BMessage(kMsgChangeClock));
524 	menu->AddItem(item);
525 
526 	item = new BMenuItem("Hide Time", new BMessage('time'));
527 	menu->AddItem(item);
528 
529 	menu->SetTargetForItems(this);
530 	// Changed to accept screen coord system point;
531 	// not constrained to this view now
532 	menu->Go(point, true, true, BRect(point.x - 4, point.y - 4,
533 		point.x + 4, point.y +4), true);
534 }
535