1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered
30trademarks of Be Incorporated in the United States and other countries. Other
31brand product names are registered trademarks or trademarks of their respective
32holders.
33All rights reserved.
34*/
35
36
37#include "TimeView.h"
38
39#include <algorithm>
40
41#include <string.h>
42
43#include <Application.h>
44#include <Catalog.h>
45#include <Debug.h>
46#include <Locale.h>
47#include <MenuItem.h>
48#include <MessageRunner.h>
49#include <PopUpMenu.h>
50#include <Roster.h>
51#include <Screen.h>
52#include <Window.h>
53
54#include "BarApp.h"
55#include "StatusView.h"
56#include "CalendarMenuWindow.h"
57
58
59static const float kHMargin = 2.0;
60
61
62#undef B_TRANSLATION_CONTEXT
63#define B_TRANSLATION_CONTEXT "TimeView"
64
65
66TTimeView::TTimeView(float maxWidth, float height)
67	:
68	BView(BRect(-100, -100, -90, -90), "_deskbar_tv_",
69		B_FOLLOW_RIGHT | B_FOLLOW_TOP,
70		B_WILL_DRAW | B_PULSE_NEEDED | B_FRAME_EVENTS),
71	fParent(NULL),
72	fMaxWidth(maxWidth),
73	fHeight(height),
74	fOrientation(true),
75	fShowLevel(0),
76	fShowSeconds(false),
77	fShowDayOfWeek(false),
78	fShowTimeZone(false),
79	fTimeFormat(NULL),
80	fDateFormat(NULL)
81{
82	fCurrentTime = fLastTime = time(NULL);
83	fSeconds = fMinute = fHour = 0;
84	fCurrentTimeStr[0] = 0;
85	fCurrentDateStr[0] = 0;
86	fLastTimeStr[0] = 0;
87	fLastDateStr[0] = 0;
88	fNeedToUpdate = true;
89	UpdateTimeFormat();
90}
91
92
93#ifdef AS_REPLICANT
94TTimeView::TTimeView(BMessage* data)
95	: BView(data),
96	fTimeFormat(NULL),
97	fDateFormat(NULL)
98{
99	fCurrentTime = fLastTime = time(NULL);
100	data->FindBool("seconds", &fShowSeconds);
101
102	UpdateTimeFormat();
103}
104#endif
105
106
107TTimeView::~TTimeView()
108{
109	delete fTimeFormat;
110	delete fDateFormat;
111}
112
113
114#ifdef AS_REPLICANT
115BArchivable*
116TTimeView::Instantiate(BMessage* data)
117{
118	if (!validate_instantiation(data, "TTimeView"))
119		return NULL;
120
121	return new TTimeView(data);
122}
123
124
125status_t
126TTimeView::Archive(BMessage* data, bool deep) const
127{
128	BView::Archive(data, deep);
129	data->AddBool("orientation", fOrientation);
130	data->AddInt16("showLevel", fShowLevel);
131	data->AddBool("showSeconds", fShowSeconds);
132	data->AddBool("showDayOfWeek", fShowDayOfWeek);
133	data->AddBool("showTimeZone", fShowTimeZone);
134	data->AddInt32("deskbar:private_align", B_ALIGN_RIGHT);
135
136	return B_OK;
137}
138#endif
139
140
141void
142TTimeView::AttachedToWindow()
143{
144	fCurrentTime = time(NULL);
145
146	SetFont(be_plain_font);
147	if (Parent()) {
148		fParent = Parent();
149		AdoptParentColors();
150	} else
151		SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
152
153	ResizeToPreferred();
154	CalculateTextPlacement();
155}
156
157
158void
159TTimeView::Draw(BRect /*updateRect*/)
160{
161	PushState();
162
163	SetHighColor(ViewColor());
164	SetLowColor(ViewColor());
165	FillRect(Bounds());
166	SetHighUIColor(B_MENU_ITEM_TEXT_COLOR);
167
168	DrawString(fCurrentTimeStr, fTimeLocation);
169
170	PopState();
171}
172
173
174void
175TTimeView::FrameMoved(BPoint)
176{
177	Update();
178}
179
180
181void
182TTimeView::GetPreferredSize(float* width, float* height)
183{
184	*height = fHeight;
185
186	float timeWidth = StringWidth(fCurrentTimeStr);
187
188	if (fOrientation) {
189		float appWidth = static_cast<TBarApp*>(be_app)->Settings()->width;
190		*width = fMaxWidth
191			= std::min(appWidth - (kDragRegionWidth + kHMargin) * 2, timeWidth);
192	} else
193		*width = fMaxWidth = timeWidth;
194}
195
196
197void
198TTimeView::MessageReceived(BMessage* message)
199{
200	switch (message->what) {
201		case kChangeTime:
202		{
203			// launch the time prefs app
204			be_roster->Launch("application/x-vnd.Haiku-Time");
205			// tell Time preflet to switch to the clock tab
206			BMessenger messenger("application/x-vnd.Haiku-Time");
207			BMessage switchToClock('SlCk');
208			messenger.SendMessage(&switchToClock);
209			break;
210		}
211
212		case kShowHideTime:
213		{
214			be_app->MessageReceived(message);
215			break;
216		}
217
218		case kShowCalendar:
219		{
220			BRect bounds(Bounds());
221			BPoint center(bounds.LeftTop());
222			center += BPoint(bounds.Width() / 2, bounds.Height() / 2);
223			ShowCalendar(center);
224			break;
225		}
226
227		default:
228			BView::MessageReceived(message);
229			break;
230	}
231}
232
233
234void
235TTimeView::MouseDown(BPoint point)
236{
237	uint32 buttons;
238
239	Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
240	if (buttons == B_SECONDARY_MOUSE_BUTTON) {
241		ShowTimeOptions(ConvertToScreen(point));
242		return;
243	} else if (buttons == B_PRIMARY_MOUSE_BUTTON)
244		ShowCalendar(point);
245
246	// invalidate last time/date strings and call the pulse
247	// method directly to change the display instantly
248	fLastDateStr[0] = '\0';
249	fLastTimeStr[0] = '\0';
250	Pulse();
251}
252
253
254void
255TTimeView::Pulse()
256{
257	time_t curTime = time(NULL);
258	tm* ct = localtime(&curTime);
259	if (ct == NULL)
260		return;
261
262	fCurrentTime = curTime;
263
264	GetCurrentTime();
265	GetCurrentDate();
266	if (strcmp(fCurrentTimeStr, fLastTimeStr) != 0) {
267		// Update bounds when the size of the strings has changed
268		Update();
269
270		strlcpy(fLastTimeStr, fCurrentTimeStr, sizeof(fLastTimeStr));
271		fNeedToUpdate = true;
272	}
273
274	// Update the tooltip if the date has changed
275	if (strcmp(fCurrentDateStr, fLastDateStr) != 0) {
276		strlcpy(fLastDateStr, fCurrentDateStr, sizeof(fLastDateStr));
277		SetToolTip(fCurrentDateStr);
278	}
279
280	if (fNeedToUpdate) {
281		fSeconds = ct->tm_sec;
282		fMinute = ct->tm_min;
283		fHour = ct->tm_hour;
284
285		Draw(Bounds());
286		fNeedToUpdate = false;
287	}
288}
289
290
291void
292TTimeView::ResizeToPreferred()
293{
294	float width;
295	float height;
296	float oldWidth = Bounds().Width();
297	float oldHeight = Bounds().Height();
298
299	GetPreferredSize(&width, &height);
300	if (height != oldHeight || width != oldWidth) {
301		ResizeTo(width, height);
302		MoveBy(oldWidth - width, 0);
303		fNeedToUpdate = true;
304	}
305}
306
307
308//	# pragma mark - Public methods
309
310
311void
312TTimeView::SetOrientation(bool orientation)
313{
314	fOrientation = orientation;
315	ResizeToPreferred();
316	CalculateTextPlacement();
317	Invalidate();
318}
319
320
321bool
322TTimeView::ShowSeconds() const
323{
324	return fShowSeconds;
325}
326
327
328void
329TTimeView::SetShowSeconds(bool show)
330{
331	fShowSeconds = show;
332	UpdateTimeFormat();
333	Update();
334}
335
336
337bool
338TTimeView::ShowDayOfWeek() const
339{
340	return fShowDayOfWeek;
341}
342
343
344void
345TTimeView::SetShowDayOfWeek(bool show)
346{
347	fShowDayOfWeek = show;
348	UpdateTimeFormat();
349	Update();
350}
351
352
353bool
354TTimeView::ShowTimeZone() const
355{
356	return fShowTimeZone;
357}
358
359
360void
361TTimeView::SetShowTimeZone(bool show)
362{
363	fShowTimeZone = show;
364	UpdateTimeFormat();
365	Update();
366}
367
368
369void
370TTimeView::ShowCalendar(BPoint where)
371{
372	if (fCalendarWindow.IsValid()) {
373		// If the calendar is already shown, just activate it
374		BMessage activate(B_SET_PROPERTY);
375		activate.AddSpecifier("Active");
376		activate.AddBool("data", true);
377
378		if (fCalendarWindow.SendMessage(&activate) == B_OK)
379			return;
380	}
381
382	where.y = Bounds().bottom + 4.0;
383	ConvertToScreen(&where);
384
385	if (where.y >= BScreen().Frame().bottom)
386		where.y -= (Bounds().Height() + 4.0);
387
388	CalendarMenuWindow* window = new CalendarMenuWindow(where);
389	fCalendarWindow = BMessenger(window);
390
391	window->Show();
392}
393
394
395//	# pragma mark - Private methods
396
397
398void
399TTimeView::UpdateTimeFormat()
400{
401	int32 fields = B_DATE_ELEMENT_HOUR | B_DATE_ELEMENT_MINUTE;
402	if (fShowSeconds)
403		fields |= B_DATE_ELEMENT_SECOND;
404	if (fShowDayOfWeek)
405		fields |= B_DATE_ELEMENT_WEEKDAY;
406	if (fShowTimeZone)
407		fields |= B_DATE_ELEMENT_TIMEZONE;
408
409	delete fTimeFormat;
410	fTimeFormat = new BDateTimeFormat(BLocale::Default());
411	fTimeFormat->SetDateTimeFormat(B_SHORT_DATE_FORMAT, B_SHORT_TIME_FORMAT, fields);
412
413	delete fDateFormat;
414	fDateFormat = new BDateFormat(BLocale::Default());
415}
416
417void
418TTimeView::GetCurrentTime()
419{
420	fTimeFormat->Format(fCurrentTimeStr, sizeof(fCurrentTimeStr), fCurrentTime,
421		B_SHORT_DATE_FORMAT, B_SHORT_TIME_FORMAT);
422}
423
424
425void
426TTimeView::GetCurrentDate()
427{
428	char tmp[sizeof(fCurrentDateStr)];
429
430	fDateFormat->Format(tmp, sizeof(fCurrentDateStr), fCurrentTime,
431		B_FULL_DATE_FORMAT);
432
433	// remove leading 0 from date when month is less than 10 (MM/DD/YY)
434	// or remove leading 0 from date when day is less than 10 (DD/MM/YY)
435	const char* str = tmp;
436	if (str[0] == '0')
437		str++;
438
439	strlcpy(fCurrentDateStr, str, sizeof(fCurrentDateStr));
440}
441
442
443void
444TTimeView::CalculateTextPlacement()
445{
446	BRect bounds(Bounds());
447
448	fDateLocation.x = 0.0;
449	fTimeLocation.x = 0.0;
450
451	BFont font;
452	GetFont(&font);
453
454	const char* stringArray[1];
455	stringArray[0] = fCurrentTimeStr;
456	BRect rectArray[1];
457	escapement_delta delta = { 0.0, 0.0 };
458	font.GetBoundingBoxesForStrings(stringArray, 1, B_SCREEN_METRIC, &delta,
459		rectArray);
460
461	fTimeLocation.y = fDateLocation.y = ceilf((bounds.Height()
462		- rectArray[0].Height() + 1.0) / 2.0 - rectArray[0].top);
463
464	if (fOrientation) {
465		float timeWidth = StringWidth(fCurrentTimeStr);
466		if (timeWidth > fMaxWidth) {
467			// time does not fit, push it over to truncate the left side
468			// to see the entire time string you must make the window wider
469			float difference = timeWidth - fMaxWidth;
470			fDateLocation.x -= difference;
471			fTimeLocation.x -= difference;
472		}
473	}
474}
475
476
477void
478TTimeView::ShowTimeOptions(BPoint point)
479{
480	BPopUpMenu* menu = new BPopUpMenu("", false, false);
481	menu->SetFont(be_plain_font);
482	BMenuItem* item;
483
484	item = new BMenuItem(B_TRANSLATE("Time preferences" B_UTF8_ELLIPSIS),
485		new BMessage(kChangeTime));
486	menu->AddItem(item);
487
488	item = new BMenuItem(B_TRANSLATE("Hide clock"),
489		new BMessage(kShowHideTime));
490	menu->AddItem(item);
491
492	item = new BMenuItem(B_TRANSLATE("Show calendar" B_UTF8_ELLIPSIS),
493		new BMessage(kShowCalendar));
494	menu->AddItem(item);
495
496	menu->SetTargetForItems(this);
497	// Changed to accept screen coord system point;
498	// not constrained to this view now
499	menu->Go(point, true, true, BRect(point.x - 4, point.y - 4,
500		point.x + 4, point.y +4), true);
501}
502
503
504void
505TTimeView::Update()
506{
507	GetCurrentTime();
508	GetCurrentDate();
509	SetToolTip(fCurrentDateStr);
510
511	ResizeToPreferred();
512	CalculateTextPlacement();
513
514	if (fParent != NULL)
515		fParent->Invalidate();
516}
517