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