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 "BarView.h"
56#include "BarWindow.h"
57#include "StatusView.h"
58#include "CalendarMenuWindow.h"
59#include "StatusView.h"
60
61
62static const float kHMargin = 2.0;
63
64
65#undef B_TRANSLATION_CONTEXT
66#define B_TRANSLATION_CONTEXT "TimeView"
67
68
69TTimeView::TTimeView(float maxWidth, float height, TBarView* barView)
70	:
71	BView(BRect(-100, -100, -90, -90), "_deskbar_tv_",
72		B_FOLLOW_RIGHT | B_FOLLOW_TOP,
73		B_WILL_DRAW | B_PULSE_NEEDED | B_FRAME_EVENTS),
74	fBarView(barView),
75	fParent(NULL),
76	fMaxWidth(maxWidth),
77	fHeight(height),
78	fShowLevel(0),
79	fShowSeconds(false),
80	fShowDayOfWeek(false),
81	fShowTimeZone(false),
82	fCalendarWindow(NULL),
83	fTimeFormat(NULL),
84	fDateFormat(NULL)
85{
86	fCurrentTime = fLastTime = time(NULL);
87	fSeconds = fMinute = fHour = 0;
88	fCurrentTimeStr[0] = 0;
89	fCurrentDateStr[0] = 0;
90	fLastTimeStr[0] = 0;
91	fLastDateStr[0] = 0;
92	fNeedToUpdate = true;
93	UpdateTimeFormat();
94}
95
96
97#ifdef AS_REPLICANT
98TTimeView::TTimeView(BMessage* data)
99	: BView(data),
100	fTimeFormat(NULL),
101	fDateFormat(NULL)
102{
103	fCurrentTime = fLastTime = time(NULL);
104	data->FindBool("seconds", &fShowSeconds);
105
106	UpdateTimeFormat();
107}
108#endif
109
110
111TTimeView::~TTimeView()
112{
113	if (fCalendarWindowMessenger.IsValid())
114		fCalendarWindowMessenger.SendMessage(B_QUIT_REQUESTED);
115
116	delete fTimeFormat;
117	delete fDateFormat;
118}
119
120
121#ifdef AS_REPLICANT
122BArchivable*
123TTimeView::Instantiate(BMessage* data)
124{
125	if (!validate_instantiation(data, "TTimeView"))
126		return NULL;
127
128	return new TTimeView(data);
129}
130
131
132status_t
133TTimeView::Archive(BMessage* data, bool deep) const
134{
135	BView::Archive(data, deep);
136	data->AddBool("orientation", Vertical());
137	data->AddInt16("showLevel", fShowLevel);
138	data->AddBool("showSeconds", fShowSeconds);
139	data->AddBool("showDayOfWeek", fShowDayOfWeek);
140	data->AddBool("showTimeZone", fShowTimeZone);
141	data->AddInt32("deskbar:private_align", B_ALIGN_RIGHT);
142
143	return B_OK;
144}
145#endif
146
147
148void
149TTimeView::AttachedToWindow()
150{
151	fCurrentTime = time(NULL);
152
153	SetFont(be_plain_font);
154	if (Parent()) {
155		fParent = Parent();
156		AdoptParentColors();
157	} else
158		SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
159
160	ResizeToPreferred();
161	CalculateTextPlacement();
162}
163
164
165void
166TTimeView::Draw(BRect /*updateRect*/)
167{
168	PushState();
169
170	SetHighColor(ViewColor());
171	SetLowColor(ViewColor());
172	FillRect(Bounds());
173	SetHighUIColor(B_MENU_ITEM_TEXT_COLOR);
174
175	DrawString(fCurrentTimeStr, fTimeLocation);
176
177	PopState();
178}
179
180
181void
182TTimeView::FrameMoved(BPoint)
183{
184	Update();
185}
186
187
188void
189TTimeView::GetPreferredSize(float* width, float* height)
190{
191	float timeWidth = StringWidth(fCurrentTimeStr);
192
193	// set the height based on the font size
194	font_height fontHeight;
195	GetFontHeight(&fontHeight);
196	fHeight = fontHeight.ascent + fontHeight.descent - 2;
197		// reduce height by 2px so that clock doesn't draw on top of border
198
199	if (Vertical()) {
200		float appWidth = static_cast<TBarApp*>(be_app)->Settings()->width;
201		*width = fMaxWidth
202			= std::min(appWidth - (gDragRegionWidth + kHMargin) * 2, timeWidth);
203	} else
204		*width = fMaxWidth = timeWidth;
205
206	*height = fHeight;
207}
208
209
210void
211TTimeView::MessageReceived(BMessage* message)
212{
213	switch (message->what) {
214		case kChangeTime:
215		{
216			// launch the time prefs app
217			be_roster->Launch("application/x-vnd.Haiku-Time");
218			// tell Time preflet to switch to the clock tab
219			BMessenger messenger("application/x-vnd.Haiku-Time");
220			BMessage switchToClock('SlCk');
221			messenger.SendMessage(&switchToClock);
222			break;
223		}
224
225		case kShowHideTime:
226		{
227			be_app->MessageReceived(message);
228			break;
229		}
230
231		case kShowCalendar:
232		{
233			BRect bounds(Bounds());
234			BPoint center(bounds.LeftTop());
235			center += BPoint(bounds.Width() / 2, bounds.Height() / 2);
236			ShowCalendar(center);
237			break;
238		}
239
240		default:
241			BView::MessageReceived(message);
242			break;
243	}
244}
245
246
247void
248TTimeView::MouseDown(BPoint point)
249{
250	uint32 buttons;
251
252	Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
253	if (buttons == B_SECONDARY_MOUSE_BUTTON) {
254		ShowTimeOptions(ConvertToScreen(point));
255		return;
256	} else if (buttons == B_PRIMARY_MOUSE_BUTTON)
257		ShowCalendar(point);
258
259	// invalidate last time/date strings and call the pulse
260	// method directly to change the display instantly
261	fLastDateStr[0] = '\0';
262	fLastTimeStr[0] = '\0';
263	Pulse();
264}
265
266
267void
268TTimeView::Pulse()
269{
270	time_t curTime = time(NULL);
271	tm* ct = localtime(&curTime);
272	if (ct == NULL)
273		return;
274
275	fCurrentTime = curTime;
276
277	GetCurrentTime();
278	GetCurrentDate();
279	if (strcmp(fCurrentTimeStr, fLastTimeStr) != 0) {
280		// Update bounds when the size of the strings has changed
281		Update();
282
283		strlcpy(fLastTimeStr, fCurrentTimeStr, sizeof(fLastTimeStr));
284		fNeedToUpdate = true;
285	}
286
287	// Update the tooltip if the date has changed
288	if (strcmp(fCurrentDateStr, fLastDateStr) != 0) {
289		strlcpy(fLastDateStr, fCurrentDateStr, sizeof(fLastDateStr));
290		SetToolTip(fCurrentDateStr);
291	}
292
293	if (fNeedToUpdate) {
294		fSeconds = ct->tm_sec;
295		fMinute = ct->tm_min;
296		fHour = ct->tm_hour;
297
298		Draw(Bounds());
299		fNeedToUpdate = false;
300	}
301}
302
303
304void
305TTimeView::ResizeToPreferred()
306{
307	float width;
308	float height;
309	float oldWidth = Bounds().Width();
310	float oldHeight = Bounds().Height();
311
312	GetPreferredSize(&width, &height);
313	if (height != oldHeight || width != oldWidth) {
314		ResizeTo(width, height);
315		MoveBy(oldWidth - width, 0);
316		fNeedToUpdate = true;
317	}
318}
319
320
321//	# pragma mark - Public methods
322
323
324bool
325TTimeView::ShowSeconds() const
326{
327	return fShowSeconds;
328}
329
330
331void
332TTimeView::SetShowSeconds(bool show)
333{
334	fShowSeconds = show;
335	UpdateTimeFormat();
336	Update();
337}
338
339
340bool
341TTimeView::ShowDayOfWeek() const
342{
343	return fShowDayOfWeek;
344}
345
346
347void
348TTimeView::SetShowDayOfWeek(bool show)
349{
350	fShowDayOfWeek = show;
351	UpdateTimeFormat();
352	Update();
353}
354
355
356bool
357TTimeView::ShowTimeZone() const
358{
359	return fShowTimeZone;
360}
361
362
363void
364TTimeView::SetShowTimeZone(bool show)
365{
366	fShowTimeZone = show;
367	UpdateTimeFormat();
368	Update();
369}
370
371
372void
373TTimeView::ShowCalendar(BPoint where)
374{
375	if (fCalendarWindowMessenger.IsValid()) {
376		// If the calendar is already shown, just activate it
377		BMessage activate(B_SET_PROPERTY);
378		activate.AddSpecifier("Active");
379		activate.AddBool("data", true);
380
381		if (fCalendarWindowMessenger.SendMessage(&activate) == B_OK)
382			return;
383	}
384
385	where.y = Bounds().bottom + 4.0;
386	ConvertToScreen(&where);
387
388	if (where.y >= BScreen().Frame().bottom)
389		where.y -= (Bounds().Height() + 4.0);
390
391	fCalendarWindow = new CalendarMenuWindow(where);
392	fCalendarWindowMessenger = BMessenger(fCalendarWindow);
393	fCalendarWindow->Show();
394}
395
396
397bool
398TTimeView::IsShowingCalendar()
399{
400	return fCalendarWindow != NULL && !fCalendarWindow->IsHidden();
401}
402
403
404//	# pragma mark - Private methods
405
406
407void
408TTimeView::UpdateTimeFormat()
409{
410	int32 fields = B_DATE_ELEMENT_HOUR | B_DATE_ELEMENT_MINUTE;
411	if (fShowSeconds)
412		fields |= B_DATE_ELEMENT_SECOND;
413	if (fShowDayOfWeek)
414		fields |= B_DATE_ELEMENT_WEEKDAY;
415	if (fShowTimeZone)
416		fields |= B_DATE_ELEMENT_TIMEZONE;
417
418	delete fTimeFormat;
419	fTimeFormat = new BDateTimeFormat(BLocale::Default());
420	fTimeFormat->SetDateTimeFormat(B_SHORT_DATE_FORMAT, B_SHORT_TIME_FORMAT,
421		fields);
422
423	delete fDateFormat;
424	fDateFormat = new BDateFormat(BLocale::Default());
425}
426
427
428void
429TTimeView::GetCurrentTime()
430{
431	fTimeFormat->Format(fCurrentTimeStr, sizeof(fCurrentTimeStr), fCurrentTime,
432		B_SHORT_DATE_FORMAT, B_SHORT_TIME_FORMAT);
433}
434
435
436void
437TTimeView::GetCurrentDate()
438{
439	char tmp[sizeof(fCurrentDateStr)];
440
441	fDateFormat->Format(tmp, sizeof(fCurrentDateStr), fCurrentTime,
442		B_FULL_DATE_FORMAT);
443
444	// remove leading 0 from date when month is less than 10 (MM/DD/YY)
445	// or remove leading 0 from date when day is less than 10 (DD/MM/YY)
446	const char* str = tmp;
447	if (str[0] == '0')
448		str++;
449
450	strlcpy(fCurrentDateStr, str, sizeof(fCurrentDateStr));
451}
452
453
454void
455TTimeView::CalculateTextPlacement()
456{
457	fDateLocation.x = 0.0;
458	fTimeLocation.x = 0.0;
459
460	BFont font;
461	GetFont(&font);
462
463	const char* stringArray[1];
464	stringArray[0] = fCurrentTimeStr;
465	BRect rectArray[1];
466	escapement_delta delta = { 0.0, 0.0 };
467	font.GetBoundingBoxesForStrings(stringArray, 1, B_SCREEN_METRIC, &delta,
468		rectArray);
469
470	// center vertically
471	fTimeLocation.y = fDateLocation.y = ceilf((Bounds().Height()
472		- rectArray[0].Height() + 1.0) / 2.0 - rectArray[0].top);
473
474	if (Vertical()) {
475		float timeWidth = StringWidth(fCurrentTimeStr);
476		if (timeWidth > fMaxWidth) {
477			// time does not fit, push it over to truncate the left side
478			// to see the entire time string you must make the window wider
479			float difference = timeWidth - fMaxWidth;
480			fDateLocation.x -= difference;
481			fTimeLocation.x -= difference;
482		}
483	}
484}
485
486
487void
488TTimeView::ShowTimeOptions(BPoint point)
489{
490	BPopUpMenu* menu = new BPopUpMenu("", false, false);
491	menu->SetFont(be_plain_font);
492	BMenuItem* item;
493
494	item = new BMenuItem(B_TRANSLATE("Time preferences" B_UTF8_ELLIPSIS),
495		new BMessage(kChangeTime));
496	menu->AddItem(item);
497
498	item = new BMenuItem(B_TRANSLATE("Hide clock"),
499		new BMessage(kShowHideTime));
500	menu->AddItem(item);
501
502	item = new BMenuItem(B_TRANSLATE("Show calendar" B_UTF8_ELLIPSIS),
503		new BMessage(kShowCalendar));
504	menu->AddItem(item);
505
506	menu->SetTargetForItems(this);
507	// Changed to accept screen coord system point;
508	// not constrained to this view now
509	menu->Go(point, true, true, BRect(point.x - 4, point.y - 4,
510		point.x + 4, point.y +4), true);
511}
512
513
514void
515TTimeView::Update()
516{
517	GetCurrentTime();
518	GetCurrentDate();
519	SetToolTip(fCurrentDateStr);
520
521	ResizeToPreferred();
522	CalculateTextPlacement();
523
524	if (fParent != NULL)
525		fParent->Invalidate();
526}
527
528
529bool
530TTimeView::Vertical()
531{
532	if (fBarView == NULL)
533		return true;
534
535	return fBarView->Vertical();
536}
537