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