xref: /haiku/src/apps/deskbar/TimeView.cpp (revision a4f6a81235ca2522c01f532de13cad9b729d4029)
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 	// see if the user holds down the button long enough to show him the calendar
339 
340 	bigtime_t startTime = system_time();
341 
342 	// use the doubleClickSpeed as a treshold
343 	bigtime_t doubleClickSpeed;
344 	get_click_speed(&doubleClickSpeed);
345 
346 	while (buttons) {
347 		BPoint where;
348 		GetMouse(&where, &buttons, false);
349 
350 		if ((system_time() - startTime) > doubleClickSpeed) {
351 			BPopUpMenu *menu = new BPopUpMenu("", false, false);
352 			menu->SetFont(be_plain_font);
353 
354 			menu->AddItem(new CalendarMenuItem());
355 			menu->ResizeToPreferred();
356 
357 			point = where;
358 			BScreen screen;
359 			where.y = Bounds().bottom + 4;
360 
361 			// make sure the menu is visible at doesn't hide the date
362 			ConvertToScreen(&where);
363 			if (where.y + menu->Bounds().Height() > screen.Frame().bottom)
364 				where.y -= menu->Bounds().Height() + 2 * Bounds().Height();
365 
366 			ConvertToScreen(&point);
367 			menu->Go(where, true, true, BRect(point.x - 4, point.y - 4,
368 				point.x + 4, point.y + 4), true);
369 			return;
370 		}
371 
372 		snooze(15000);
373 	}
374 #endif
375 }
376 
377 
378 void
379 TTimeView::Pulse()
380 {
381 	time_t curTime = time(NULL);
382 	tm	ct = *localtime(&curTime);
383 	fTime = curTime;
384 
385 	GetCurrentTime();
386 	GetCurrentDate();
387 	if ((!fShowingDate && strcmp(fTimeStr, fLastTimeStr) != 0)
388 		|| 	(fShowingDate && strcmp(fDateStr, fLastDateStr) != 0)) {
389 		// Update bounds when the size of the strings has changed
390 		// For dates, Update() could be called two times in a row,
391 		// but that should only happen very rarely
392 		if ((!fShowingDate && fLastTimeStr[1] != fTimeStr[1]
393 				&& (fLastTimeStr[1] == ':' || fTimeStr[1] == ':'))
394 			|| (fShowingDate && strlen(fDateStr) != strlen(fLastDateStr))
395 			|| !fLastTimeStr[0])
396 			Update();
397 
398 		strcpy(fLastTimeStr, fTimeStr);
399 		strcpy(fLastDateStr, fDateStr);
400 		fNeedToUpdate = true;
401 	}
402 
403 	if (fShowingDate && (fLastTime + 5 <= time(NULL))) {
404 		fShowingDate = false;
405 		Update();	// Needs to happen since size can change here
406 	}
407 
408 	if (fNeedToUpdate) {
409 		fSeconds = ct.tm_sec;
410 		fMinute = ct.tm_min;
411 		fHour = ct.tm_hour;
412 		fInterval = ct.tm_hour >= 12;
413 
414 		Draw(Bounds());
415 		fNeedToUpdate = false;
416 	}
417 }
418 
419 
420 void
421 TTimeView::ShowSeconds(bool on)
422 {
423 	fShowSeconds = on;
424 	Update();
425 }
426 
427 
428 void
429 TTimeView::ShowMilTime(bool on)
430 {
431 	fMilTime = on;
432 	Update();
433 }
434 
435 
436 void
437 TTimeView::ShowDate(bool on)
438 {
439 	fShowingDate = on;
440 	Update();
441 }
442 
443 
444 void
445 TTimeView::ShowFullDate(bool on)
446 {
447 	fFullDate = on;
448 	Update();
449 }
450 
451 
452 void
453 TTimeView::ShowEuroDate(bool on)
454 {
455 	fEuroDate = on;
456 	Update();
457 }
458 
459 
460 void
461 TTimeView::AllowFullDate(bool allow)
462 {
463 	fCanShowFullDate = allow;
464 
465 	if (allow != ShowingFullDate())
466 		Update();
467 }
468 
469 
470 void
471 TTimeView::Update()
472 {
473 	GetCurrentTime();
474 	GetCurrentDate();
475 
476 	ResizeToPreferred();
477 	CalculateTextPlacement();
478 
479 	if (fParent) {
480 		fParent->MessageReceived(new BMessage('trfm'));
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