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