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