xref: /haiku/src/apps/deskbar/TimeView.cpp (revision 746cac055adc6ac3308c7bc2d29040fb95689cc9)
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 #ifdef _SHOW_CALENDAR_MENU_WINDOW
43 #	include "CalendarMenuWindow.h"
44 #endif
45 
46 #include <Debug.h>
47 #include <MenuItem.h>
48 #include <MessageRunner.h>
49 #include <PopUpMenu.h>
50 #include <Roster.h>
51 #include <Window.h>
52 #include <Screen.h>
53 
54 #include <string.h>
55 
56 
57 const char *kShortDateFormat = "%m/%d/%y";
58 const char *kShortEuroDateFormat = "%d/%m/%y";
59 const char *kLongDateFormat = "%a, %B %d, %Y";
60 const char *kLongEuroDateFormat = "%a, %d %B, %Y";
61 
62 static const char * const kMinString = "99:99 AM";
63 static const float kHMargin = 2.0;
64 
65 
66 enum {
67 	kMsgShowClock,
68 	kMsgChangeClock,
69 	kMsgHide,
70 	kMsgLongClick,
71 	kMsgShowCalendar
72 };
73 
74 
75 TTimeView::TTimeView(float maxWidth, float height, bool showSeconds, bool milTime, bool fullDate, bool euroDate, bool)
76 	: 	BView(BRect(-100,-100,-90,-90), "_deskbar_tv_",
77 	B_FOLLOW_RIGHT | B_FOLLOW_TOP,
78 	B_WILL_DRAW | B_PULSE_NEEDED | B_FRAME_EVENTS),
79 	fParent(NULL),
80 	fShowInterval(true), // ToDo: defaulting this to true until UI is in place
81 	fShowSeconds(showSeconds),
82 	fMilTime(milTime),
83 	fFullDate(fullDate),
84 	fCanShowFullDate(false),
85 	fEuroDate(euroDate),
86 	fMaxWidth(maxWidth),
87 	fHeight(height),
88 	fOrientation(true),
89 	fLongClickMessageRunner(NULL)
90 {
91 	fShowingDate = false;
92 	fTime = fLastTime = time(NULL);
93 	fSeconds = fMinute = fHour = 0;
94 	fLastTimeStr[0] = 0;
95 	fLastDateStr[0] = 0;
96 	fNeedToUpdate = true;
97 }
98 
99 
100 #ifdef AS_REPLICANT
101 TTimeView::TTimeView(BMessage *data)
102 	: BView(data)
103 {
104 	fTime = fLastTime = time(NULL);
105 	data->FindBool("seconds", &fShowSeconds);
106 	data->FindBool("miltime", &fMilTime);
107 	data->FindBool("fulldate", &fFullDate);
108 	data->FindBool("eurodate", &fEuroDate);
109 	data->FindBool("interval", &fInterval);
110 	fShowingDate = false;
111 }
112 #endif
113 
114 
115 TTimeView::~TTimeView()
116 {
117 	StopLongClickNotifier();
118 }
119 
120 
121 #ifdef AS_REPLICANT
122 BArchivable*
123 TTimeView::Instantiate(BMessage *data)
124 {
125 	if (!validate_instantiation(data, "TTimeView"))
126 		return NULL;
127 
128 	return new TTimeView(data);
129 }
130 
131 
132 status_t
133 TTimeView::Archive(BMessage *data, bool deep) const
134 {
135 	BView::Archive(data, deep);
136 	data->AddBool("seconds", fShowSeconds);
137 	data->AddBool("miltime", fMilTime);
138 	data->AddBool("fulldate", fFullDate);
139 	data->AddBool("eurodate", fEuroDate);
140 	data->AddBool("interval", fInterval);
141 	data->AddInt32("deskbar:private_align", B_ALIGN_RIGHT);
142 
143 	return B_OK;
144 }
145 #endif
146 
147 
148 void
149 TTimeView::AttachedToWindow()
150 {
151 	fTime = time(NULL);
152 
153 	SetFont(be_plain_font);
154 	if (Parent()) {
155 		fParent = Parent();
156 		SetViewColor(Parent()->ViewColor());
157 	} else
158 		SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
159 
160 	ResizeToPreferred();
161 	CalculateTextPlacement();
162 }
163 
164 
165 void
166 TTimeView::GetPreferredSize(float *width, float *height)
167 {
168 	*height = fHeight;
169 
170 	GetCurrentTime();
171 	GetCurrentDate();
172 
173 	// TODO: SetOrientation never gets called, fix that
174 	// When in vertical mode, we want to limit the width so that it can't
175 	// overlap the bevels in the parent view.
176 	if (ShowingDate())
177 		*width = fOrientation ?
178 			 min_c(fMaxWidth - kHMargin, kHMargin + StringWidth(fDateStr))
179 			 : kHMargin + StringWidth(fDateStr);
180 	else {
181 		*width = fOrientation ?
182 			 min_c(fMaxWidth - kHMargin, kHMargin + StringWidth(fTimeStr))
183 			 : kHMargin + StringWidth(fTimeStr);
184 	}
185 }
186 
187 
188 void
189 TTimeView::ResizeToPreferred()
190 {
191 	float width, height;
192 	float oldWidth = Bounds().Width(), oldHeight = Bounds().Height();
193 
194 	GetPreferredSize(&width, &height);
195 	if (height != oldHeight || width != oldWidth) {
196 		ResizeTo(width, height);
197 		MoveBy(oldWidth - width, 0);
198 		fNeedToUpdate = true;
199 	}
200 }
201 
202 
203 void
204 TTimeView::FrameMoved(BPoint)
205 {
206 	Update();
207 }
208 
209 
210 void
211 TTimeView::MessageReceived(BMessage* message)
212 {
213 	switch (message->what) {
214 		case kMsgFullDate:
215 			ShowFullDate(!ShowingFullDate());
216 			break;
217 
218 		case kMsgShowSeconds:
219 			ShowSeconds(!ShowingSeconds());
220 			break;
221 
222 		case kMsgMilTime:
223 			ShowMilTime(!ShowingMilTime());
224 			break;
225 
226 		case kMsgEuroDate:
227 			ShowEuroDate(!ShowingEuroDate());
228 			break;
229 
230 		case kMsgChangeClock:
231 			// launch the time prefs app
232 			be_roster->Launch("application/x-vnd.Haiku-Time");
233 			break;
234 
235 		case 'time':
236 			Window()->PostMessage(message, Parent());
237 			break;
238 
239 		case kMsgLongClick:
240 		{
241 			StopLongClickNotifier();
242 			BPoint where;
243 			message->FindPoint("where", &where);
244 			ShowCalendar(where);
245 			break;
246 		}
247 
248 		case kMsgShowCalendar:
249 		{
250 			BRect bounds(Bounds());
251 			BPoint center(bounds.LeftTop());
252 			center += BPoint(bounds.Width() / 2, bounds.Height() / 2);
253 			ShowCalendar(center);
254 			break;
255 		}
256 
257 		default:
258 			BView::MessageReceived(message);
259 	}
260 }
261 
262 
263 void
264 TTimeView::ShowCalendar(BPoint where)
265 {
266 	//TODO: do nothing if the calendar is already shown
267 
268 #ifdef _SHOW_CALENDAR_MENU_ITEM
269 
270 	BPopUpMenu *menu = new BPopUpMenu("", false, false);
271 	menu->SetFont(be_plain_font);
272 
273 	menu->AddItem(new CalendarMenuItem());
274 	menu->ResizeToPreferred();
275 
276 	BPoint point = where;
277 	BScreen screen;
278 	where.y = Bounds().bottom + 4;
279 
280 	// make sure the menu is visible and doesn't hide the date
281 	ConvertToScreen(&where);
282 	if (where.y + menu->Bounds().Height() > screen.Frame().bottom)
283 		where.y -= menu->Bounds().Height() + 2 * Bounds().Height();
284 
285 	ConvertToScreen(&point);
286 	menu->Go(where, true, true, BRect(point.x - 4, point.y - 4,
287 		point.x + 4, point.y + 4), true);
288 
289 #elif _SHOW_CALENDAR_MENU_WINDOW
290 
291 	where.y = Bounds().bottom + 4.0;
292 	ConvertToScreen(&where);
293 
294 	if (where.y >= BScreen().Frame().bottom)
295 		where.y -= (Bounds().Height() + 4.0);
296 
297 	CalendarMenuWindow* window = new CalendarMenuWindow(where, fEuroDate);
298 	window->Show();
299 #endif
300 }
301 
302 
303 void
304 TTimeView::StartLongClickNotifier(BPoint where)
305 {
306 	StopLongClickNotifier();
307 
308 	BMessage longClickMessage(kMsgLongClick);
309 	longClickMessage.AddPoint("where", where);
310 
311 	bigtime_t longClickThreshold;
312 	get_click_speed(&longClickThreshold);
313 		// use the doubleClickSpeed as a threshold
314 
315 	fLongClickMessageRunner = new BMessageRunner(BMessenger(this),
316 		 &longClickMessage, longClickThreshold, 1);
317 }
318 
319 
320 void
321 TTimeView::StopLongClickNotifier()
322 {
323 	delete fLongClickMessageRunner;
324 	fLongClickMessageRunner = NULL;
325 }
326 
327 
328 void
329 TTimeView::GetCurrentTime()
330 {
331 	char tmp[64];
332 	tm time = *localtime(&fTime);
333 
334 	if (fMilTime) {
335 		strftime(tmp, 64, fShowSeconds ? "%H:%M:%S" : "%H:%M", &time);
336 	} else {
337 		if (fShowInterval)
338 			strftime(tmp, 64, fShowSeconds ? "%I:%M:%S %p" : "%I:%M %p", &time);
339 		else
340 			strftime(tmp, 64, fShowSeconds ? "%I:%M:%S" : "%I:%M", &time);
341 	}
342 
343 	//	remove leading 0 from time when hour is less than 10
344 	const char *str = tmp;
345 	if (str[0] == '0')
346 		str++;
347 
348 	strcpy(fTimeStr, str);
349 
350 	fSeconds = time.tm_sec;
351 	fMinute = time.tm_min;
352 	fHour = time.tm_hour;
353 }
354 
355 
356 void
357 TTimeView::GetCurrentDate()
358 {
359 	char tmp[64];
360 	tm time = *localtime(&fTime);
361 
362 	if (fFullDate && CanShowFullDate())
363 		strftime(tmp, 64, fEuroDate ? kLongEuroDateFormat : kLongDateFormat, &time);
364 	else
365 		strftime(tmp, 64, fEuroDate ? kShortEuroDateFormat : kShortDateFormat, &time);
366 
367 	//	remove leading 0 from date when month is less than 10 (MM/DD/YY)
368 	//  or remove leading 0 from date when day is less than 10 (DD/MM/YY)
369 	const char* str = tmp;
370 	if (str[0] == '0')
371 		str++;
372 
373 	strcpy(fDateStr, str);
374 }
375 
376 
377 void
378 TTimeView::Draw(BRect /*updateRect*/)
379 {
380 	PushState();
381 
382 	SetHighColor(ViewColor());
383 	SetLowColor(ViewColor());
384 	FillRect(Bounds());
385 	SetHighColor(0, 0, 0, 255);
386 
387 	if (fShowingDate)
388 		DrawString(fDateStr, fDateLocation);
389 	else
390 		DrawString(fTimeStr, fTimeLocation);
391 
392 	PopState();
393 }
394 
395 
396 void
397 TTimeView::MouseDown(BPoint point)
398 {
399 	uint32 buttons;
400 
401 	Window()->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons);
402 	if (buttons == B_SECONDARY_MOUSE_BUTTON) {
403 		ShowClockOptions(ConvertToScreen(point));
404 		return;
405 	} else if (buttons == B_PRIMARY_MOUSE_BUTTON) {
406 		StartLongClickNotifier(point);
407 	}
408 
409 	//	flip to/from showing date or time
410 	fShowingDate = !fShowingDate;
411 	if (fShowingDate)
412 		fLastTime = time(NULL);
413 
414 	// invalidate last time/date strings and call the pulse
415 	// method directly to change the display instantly
416 	fLastDateStr[0] = '\0';
417 	fLastTimeStr[0] = '\0';
418 	Pulse();
419 }
420 
421 
422 void
423 TTimeView::MouseUp(BPoint point)
424 {
425 	StopLongClickNotifier();
426 }
427 
428 
429 void
430 TTimeView::Pulse()
431 {
432 	time_t curTime = time(NULL);
433 	tm	ct = *localtime(&curTime);
434 	fTime = curTime;
435 
436 	GetCurrentTime();
437 	GetCurrentDate();
438 	if ((!fShowingDate && strcmp(fTimeStr, fLastTimeStr) != 0)
439 		|| 	(fShowingDate && strcmp(fDateStr, fLastDateStr) != 0)) {
440 		// Update bounds when the size of the strings has changed
441 		// For dates, Update() could be called two times in a row,
442 		// but that should only happen very rarely
443 		if ((!fShowingDate && fLastTimeStr[1] != fTimeStr[1]
444 				&& (fLastTimeStr[1] == ':' || fTimeStr[1] == ':'))
445 			|| (fShowingDate && strlen(fDateStr) != strlen(fLastDateStr))
446 			|| !fLastTimeStr[0])
447 			Update();
448 
449 		strcpy(fLastTimeStr, fTimeStr);
450 		strcpy(fLastDateStr, fDateStr);
451 		fNeedToUpdate = true;
452 	}
453 
454 	if (fShowingDate && (fLastTime + 5 <= time(NULL))) {
455 		fShowingDate = false;
456 		Update();	// Needs to happen since size can change here
457 	}
458 
459 	if (fNeedToUpdate) {
460 		fSeconds = ct.tm_sec;
461 		fMinute = ct.tm_min;
462 		fHour = ct.tm_hour;
463 		fInterval = ct.tm_hour >= 12;
464 
465 		Draw(Bounds());
466 		fNeedToUpdate = false;
467 	}
468 }
469 
470 
471 void
472 TTimeView::ShowSeconds(bool on)
473 {
474 	fShowSeconds = on;
475 	Update();
476 }
477 
478 
479 void
480 TTimeView::ShowMilTime(bool on)
481 {
482 	fMilTime = on;
483 	Update();
484 }
485 
486 
487 void
488 TTimeView::ShowDate(bool on)
489 {
490 	fShowingDate = on;
491 	Update();
492 }
493 
494 
495 void
496 TTimeView::ShowFullDate(bool on)
497 {
498 	fFullDate = on;
499 	Update();
500 }
501 
502 
503 void
504 TTimeView::ShowEuroDate(bool on)
505 {
506 	fEuroDate = on;
507 	Update();
508 }
509 
510 
511 void
512 TTimeView::AllowFullDate(bool allow)
513 {
514 	fCanShowFullDate = allow;
515 
516 	if (allow != ShowingFullDate())
517 		Update();
518 }
519 
520 
521 void
522 TTimeView::Update()
523 {
524 	GetCurrentTime();
525 	GetCurrentDate();
526 
527 	ResizeToPreferred();
528 	CalculateTextPlacement();
529 
530 	if (fParent) {
531 		BMessage reformat('Trfm');
532 		fParent->MessageReceived(&reformat);
533 			// time string format realign
534 		fParent->Invalidate();
535 	}
536 }
537 
538 
539 void
540 TTimeView::SetOrientation(bool o)
541 {
542 	fOrientation = o;
543 	CalculateTextPlacement();
544 	Invalidate();
545 }
546 
547 
548 void
549 TTimeView::CalculateTextPlacement()
550 {
551 	BRect bounds(Bounds());
552 
553 	fDateLocation.x = 0.0;
554 	fTimeLocation.x = 0.0;
555 
556 	BFont font;
557 	GetFont(&font);
558 	const char* stringArray[1];
559 	stringArray[0] = fTimeStr;
560 	BRect rectArray[1];
561 	escapement_delta delta = { 0.0, 0.0 };
562 	font.GetBoundingBoxesForStrings(stringArray, 1, B_SCREEN_METRIC, &delta,
563 		 rectArray);
564 
565 	fTimeLocation.y = fDateLocation.y = ceilf((bounds.Height() -
566 		rectArray[0].Height() + 1.0) / 2.0 - rectArray[0].top);
567 }
568 
569 
570 void
571 TTimeView::ShowClockOptions(BPoint point)
572 {
573 	BPopUpMenu *menu = new BPopUpMenu("", false, false);
574 	menu->SetFont(be_plain_font);
575 	BMenuItem *item;
576 
577 	item = new BMenuItem("Change Time" B_UTF8_ELLIPSIS, new BMessage(kMsgChangeClock));
578 	menu->AddItem(item);
579 
580 	item = new BMenuItem("Hide Time", new BMessage('time'));
581 	menu->AddItem(item);
582 
583 #if defined(_SHOW_CALENDAR_MENU_ITEM) || defined(_SHOW_CALENDAR_MENU_WINDOW)
584 	item = new BMenuItem("Show Calendar" B_UTF8_ELLIPSIS, new BMessage(kMsgShowCalendar));
585 	menu->AddItem(item);
586 #endif
587 
588 	menu->SetTargetForItems(this);
589 	// Changed to accept screen coord system point;
590 	// not constrained to this view now
591 	menu->Go(point, true, true, BRect(point.x - 4, point.y - 4,
592 		point.x + 4, point.y +4), true);
593 }
594