xref: /haiku/src/preferences/time/AnalogClock.cpp (revision ddac407426cd3b3d0b4589d7a161b300b3539a2a)
1 /*
2  * Copyright 2004-2009, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Mike Berg <mike@berg-net.us>
7  *		Julun <host.haiku@gmx.de>
8  *		Stephan Aßmus <superstippi@gmx.de>
9  *		Clemens <mail@Clemens-Zeidler.de>
10  */
11 
12 #include "AnalogClock.h"
13 
14 #include <math.h>
15 #include <stdio.h>
16 
17 #include <Bitmap.h>
18 #include <Message.h>
19 #include <Window.h>
20 
21 #include "TimeMessages.h"
22 
23 
24 #define DRAG_DELTA_PHI 0.2
25 
26 
27 class OffscreenClock : public BView {
28 public:
29 							OffscreenClock(BRect frame, const char *name);
30 	virtual					~OffscreenClock();
31 
32 			void		 	SetTime(int32 hour, int32 minute, int32 second);
33 			void 			GetTime(int32 *hour, int32 *minute, int32 *second);
34 			bool			IsDirty() const	{	return fDirty;	}
35 			void 			DrawClock();
36 
37 			bool			InHourHand(BPoint point);
38 			bool			InMinuteHand(BPoint point);
39 
40 			void			SetHourHand(BPoint point);
41 			void			SetMinuteHand(BPoint point);
42 
43 			void			SetHourDragging(bool dragging);
44 			void			SetMinuteDragging(bool dragging);
45 
46 private:
47 			float			_GetPhi(BPoint point);
48 			bool			_InHand(BPoint point, int32 ticks, float radius);
49 			void			_DrawHands(float x, float y, float radius,
50 								rgb_color hourHourColor,
51 								rgb_color hourMinuteColor,
52 								rgb_color secondsColor, rgb_color knobColor);
53 
54 			int32			fHours;
55 			int32			fMinutes;
56 			int32			fSeconds;
57 			bool			fDirty;
58 
59 			float			fCenterX;
60 			float			fCenterY;
61 			float			fRadius;
62 
63 			bool			fHourDragging;
64 			bool			fMinuteDragging;
65 };
66 
67 
68 OffscreenClock::OffscreenClock(BRect frame, const char *name)
69 	:
70 	BView(frame, name, B_FOLLOW_NONE, B_WILL_DRAW),
71 	fHours(0),
72 	fMinutes(0),
73 	fSeconds(0),
74 	fDirty(true),
75 	fHourDragging(false),
76 	fMinuteDragging(false)
77 {
78 	SetFlags(Flags() | B_SUBPIXEL_PRECISE);
79 
80 	BRect bounds = Bounds();
81 	fCenterX = floorf((bounds.left + bounds.right) / 2 + 0.5) + 0.5;
82 	fCenterY = floorf((bounds.top + bounds.bottom) / 2 + 0.5) + 0.5;
83 		// + 0.5 is for the offset to pixel centers
84 		// (important when drawing with B_SUBPIXEL_PRECISE)
85 
86 	fRadius = floorf((MIN(bounds.Width(), bounds.Height()) / 2.0)) - 2.5;
87 	fRadius -= 3;
88 }
89 
90 
91 OffscreenClock::~OffscreenClock()
92 {
93 }
94 
95 
96 void
97 OffscreenClock::SetTime(int32 hour, int32 minute, int32 second)
98 {
99 	if (fHours == hour && fMinutes == minute && fSeconds == second)
100 		return;
101 
102 	fHours = hour;
103 	fMinutes = minute;
104 	fSeconds = second;
105 
106 	fDirty = true;
107 }
108 
109 
110 void
111 OffscreenClock::GetTime(int32 *hour, int32 *minute, int32 *second)
112 {
113 	*hour = fHours;
114 	*minute = fMinutes;
115 	*second = fSeconds;
116 }
117 
118 
119 void
120 OffscreenClock::DrawClock()
121 {
122 	if (!LockLooper())
123 		return;
124 
125 	BRect bounds = Bounds();
126 	// clear background
127 	rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR);
128 	SetHighColor(background);
129 	FillRect(bounds);
130 
131 
132 	bounds.Set(fCenterX - fRadius, fCenterY - fRadius,
133 		fCenterX + fRadius, fCenterY + fRadius);
134 
135 	SetPenSize(2.0);
136 
137 	SetHighColor(tint_color(background, B_DARKEN_1_TINT));
138 	StrokeEllipse(bounds.OffsetByCopy(-1, -1));
139 
140 	SetHighColor(tint_color(background, B_LIGHTEN_2_TINT));
141 	StrokeEllipse(bounds.OffsetByCopy(1, 1));
142 
143 	SetHighColor(tint_color(background, B_DARKEN_3_TINT));
144 	StrokeEllipse(bounds);
145 
146 	SetLowColor(255, 255, 255);
147 	FillEllipse(bounds, B_SOLID_LOW);
148 
149 	SetHighColor(tint_color(HighColor(), B_DARKEN_2_TINT));
150 
151 	// minutes
152 	SetPenSize(1.0);
153 	SetLineMode(B_BUTT_CAP, B_MITER_JOIN);
154 	for (int32 minute = 1; minute < 60; minute++) {
155 		if (minute % 5 == 0)
156 			continue;
157 		float x1 = fCenterX + sinf(minute * M_PI / 30.0) * fRadius;
158 		float y1 = fCenterY + cosf(minute * M_PI / 30.0) * fRadius;
159 		float x2 = fCenterX + sinf(minute * M_PI / 30.0) * (fRadius * 0.95);
160 		float y2 = fCenterY + cosf(minute * M_PI / 30.0) * (fRadius * 0.95);
161 		StrokeLine(BPoint(x1, y1), BPoint(x2, y2));
162 	}
163 
164 	SetHighColor(tint_color(HighColor(), B_DARKEN_1_TINT));
165 
166 	// hours
167 	SetPenSize(2.0);
168 	SetLineMode(B_ROUND_CAP, B_MITER_JOIN);
169 	for (int32 hour = 0; hour < 12; hour++) {
170 		float x1 = fCenterX + sinf(hour * M_PI / 6.0) * fRadius;
171 		float y1 = fCenterY + cosf(hour * M_PI / 6.0) * fRadius;
172 		float x2 = fCenterX + sinf(hour * M_PI / 6.0) * (fRadius * 0.9);
173 		float y2 = fCenterY + cosf(hour * M_PI / 6.0) * (fRadius * 0.9);
174 		StrokeLine(BPoint(x1, y1), BPoint(x2, y2));
175 	}
176 
177 	rgb_color knobColor = tint_color(HighColor(), B_DARKEN_2_TINT);;
178 	rgb_color hourColor;
179 	if (fHourDragging)
180 		hourColor = (rgb_color){ 0, 0, 255, 255 };
181 	else
182 	 	hourColor = tint_color(HighColor(), B_DARKEN_2_TINT);
183 
184 	rgb_color minuteColor;
185 	if (fMinuteDragging)
186 		minuteColor = (rgb_color){ 0, 0, 255, 255 };
187 	else
188 	 	minuteColor = tint_color(HighColor(), B_DARKEN_2_TINT);
189 
190 	rgb_color secondsColor = (rgb_color){ 255, 0, 0, 255 };
191 	rgb_color shadowColor = tint_color(LowColor(),
192 		(B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2);
193 
194 	_DrawHands(fCenterX + 1.5, fCenterY + 1.5, fRadius,
195 		shadowColor, shadowColor, shadowColor, shadowColor);
196 	_DrawHands(fCenterX, fCenterY, fRadius,
197 		hourColor, minuteColor, secondsColor, knobColor);
198 
199 	Sync();
200 
201 	UnlockLooper();
202 }
203 
204 
205 bool
206 OffscreenClock::InHourHand(BPoint point)
207 {
208 	int32 ticks = fHours;
209 	if (ticks > 12)
210 		ticks -= 12;
211 	ticks *= 5;
212 	ticks += int32(5. * fMinutes / 60.0);
213 	if (ticks > 60)
214 		ticks -= 60;
215 	return _InHand(point, ticks, fRadius * 0.7);
216 }
217 
218 
219 bool
220 OffscreenClock::InMinuteHand(BPoint point)
221 {
222 	return _InHand(point, fMinutes, fRadius * 0.9);
223 }
224 
225 
226 void
227 OffscreenClock::SetHourHand(BPoint point)
228 {
229 	point.x -= fCenterX;
230 	point.y -= fCenterY;
231 
232 	float pointPhi = _GetPhi(point);
233 	float hoursExact = 6.0 * pointPhi / M_PI;
234 	if (fHours >= 12)
235 		fHours = 12;
236 	else
237 		fHours = 0;
238 	fHours += int32(hoursExact);
239 
240 	SetTime(fHours, fMinutes, fSeconds);
241 }
242 
243 
244 void
245 OffscreenClock::SetMinuteHand(BPoint point)
246 {
247 	point.x -= fCenterX;
248 	point.y -= fCenterY;
249 
250 	float pointPhi = _GetPhi(point);
251 	float minutesExact = 30.0 * pointPhi / M_PI;
252 	fMinutes = int32(ceilf(minutesExact));
253 
254 	SetTime(fHours, fMinutes, fSeconds);
255 }
256 
257 
258 void
259 OffscreenClock::SetHourDragging(bool dragging)
260 {
261 	fHourDragging = dragging;
262 	fDirty = true;
263 }
264 
265 
266 void
267 OffscreenClock::SetMinuteDragging(bool dragging)
268 {
269 	fMinuteDragging = dragging;
270 	fDirty = true;
271 }
272 
273 
274 float
275 OffscreenClock::_GetPhi(BPoint point)
276 {
277 	if (point.x == 0 && point.y < 0)
278 		return 2 * M_PI;
279 	if (point.x == 0 && point.y > 0)
280 		return M_PI;
281 	if (point.y == 0 && point.x < 0)
282 		return M_PI * 3 / 2;
283 	if (point.y == 0 && point.x > 0)
284 		return M_PI / 2;
285 
286 	float pointPhi = atanf(-1. * point.y / point.x);
287 	if (point.y < 0. && point.x > 0.)	// right upper corner
288 		pointPhi = M_PI / 2. - pointPhi;
289 	if (point.y > 0. && point.x > 0.)	// right lower corner
290 		pointPhi = M_PI / 2 - pointPhi;
291 	if (point.y > 0. && point.x < 0.)	// left lower corner
292 		pointPhi = (M_PI * 3. / 2. - pointPhi);
293 	if (point.y < 0. && point.x < 0.)	// left upper corner
294 		pointPhi = 3. / 2. * M_PI - pointPhi;
295 	return pointPhi;
296 }
297 
298 
299 bool
300 OffscreenClock::_InHand(BPoint point, int32 ticks, float radius)
301 {
302 	point.x -= fCenterX;
303 	point.y -= fCenterY;
304 
305 	float pRadius = sqrt(pow(point.x, 2) + pow(point.y, 2));
306 
307 	if (radius < pRadius)
308 		return false;
309 
310 	float pointPhi = _GetPhi(point);
311 	float handPhi = M_PI / 30.0 * ticks;
312 	float delta = pointPhi - handPhi;
313 	if (fabs(delta) > DRAG_DELTA_PHI)
314 		return false;
315 
316 	return true;
317 }
318 
319 
320 void
321 OffscreenClock::_DrawHands(float x, float y, float radius,
322 	rgb_color hourColor,
323 	rgb_color minuteColor,
324 	rgb_color secondsColor,
325 	rgb_color knobColor)
326 {
327 	float offsetX;
328 	float offsetY;
329 
330 	// calc, draw hour hand
331 	SetHighColor(hourColor);
332 	SetPenSize(4.0);
333 	float hours = fHours + float(fMinutes) / 60.0;
334 	offsetX = (radius * 0.7) * sinf((hours * M_PI) / 6.0);
335 	offsetY = (radius * 0.7) * cosf((hours * M_PI) / 6.0);
336 	StrokeLine(BPoint(x, y), BPoint(x + offsetX, y - offsetY));
337 
338 	// calc, draw minute hand
339 	SetHighColor(minuteColor);
340 	SetPenSize(3.0);
341 	float minutes = fMinutes + float(fSeconds) / 60.0;
342 	offsetX = (radius * 0.9) * sinf((minutes * M_PI) / 30.0);
343 	offsetY = (radius * 0.9) * cosf((minutes * M_PI) / 30.0);
344 	StrokeLine(BPoint(x, y), BPoint(x + offsetX, y - offsetY));
345 
346 	// calc, draw second hand
347 	SetHighColor(secondsColor);
348 	SetPenSize(1.0);
349 	offsetX = (radius * 0.95) * sinf((fSeconds * M_PI) / 30.0);
350 	offsetY = (radius * 0.95) * cosf((fSeconds * M_PI) / 30.0);
351 	StrokeLine(BPoint(x, y), BPoint(x + offsetX, y - offsetY));
352 
353 	// draw the center knob
354 	SetHighColor(knobColor);
355 	FillEllipse(BPoint(x, y), radius * 0.06, radius * 0.06);
356 }
357 
358 
359 //	#pragma mark -
360 
361 
362 TAnalogClock::TAnalogClock(BRect frame, const char *name)
363 	:
364 	BView(frame, name, B_FOLLOW_NONE, B_WILL_DRAW | B_DRAW_ON_CHILDREN),
365 	fBitmap(NULL),
366 	fClock(NULL),
367 	fDraggingHourHand(false),
368 	fDraggingMinuteHand(false),
369 	fTimeChangeIsOngoing(false)
370 {
371 	_InitView(frame);
372 }
373 
374 
375 TAnalogClock::~TAnalogClock()
376 {
377 	delete fBitmap;
378 }
379 
380 
381 void
382 TAnalogClock::_InitView(BRect rect)
383 {
384 	fClock = new OffscreenClock(Bounds(), "offscreen");
385 	fBitmap = new BBitmap(Bounds(), B_RGB32, true);
386 	fBitmap->Lock();
387 	fBitmap->AddChild(fClock);
388 	fBitmap->Unlock();
389 }
390 
391 
392 void
393 TAnalogClock::AttachedToWindow()
394 {
395 	SetViewColor(B_TRANSPARENT_COLOR);
396 }
397 
398 
399 void
400 TAnalogClock::MessageReceived(BMessage *message)
401 {
402 	int32 change;
403 	switch (message->what) {
404 		case B_OBSERVER_NOTICE_CHANGE:
405 			message->FindInt32(B_OBSERVE_WHAT_CHANGE, &change);
406 			switch (change) {
407 				case H_TM_CHANGED:
408 				{
409 					int32 hour = 0;
410 					int32 minute = 0;
411 					int32 second = 0;
412 					if (message->FindInt32("hour", &hour) == B_OK
413 					 && message->FindInt32("minute", &minute) == B_OK
414 					 && message->FindInt32("second", &second) == B_OK)
415 						SetTime(hour, minute, second);
416 					break;
417 				}
418 				default:
419 					BView::MessageReceived(message);
420 					break;
421 			}
422 		break;
423 		default:
424 			BView::MessageReceived(message);
425 			break;
426 	}
427 }
428 
429 
430 void
431 TAnalogClock::MouseDown(BPoint point)
432 {
433 	fDraggingMinuteHand = fClock->InMinuteHand(point);
434 	if (fDraggingMinuteHand) {
435 		fClock->SetMinuteDragging(true);
436 		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
437 		Invalidate();
438 		return;
439 	}
440 	fDraggingHourHand = fClock->InHourHand(point);
441 	if (fDraggingHourHand) {
442 		fClock->SetHourDragging(true);
443 		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
444 		Invalidate();
445 		return;
446 	}
447 }
448 
449 
450 void
451 TAnalogClock::MouseUp(BPoint point)
452 {
453 	if (fDraggingHourHand || fDraggingMinuteHand) {
454 		int32 hour, minute, second;
455 		fClock->GetTime(&hour, &minute, &second);
456 		BMessage message(H_USER_CHANGE);
457 		message.AddBool("time", true);
458 		message.AddInt32("hour", hour);
459 		message.AddInt32("minute", minute);
460 		Window()->PostMessage(&message);
461 		fTimeChangeIsOngoing = true;
462 	}
463 	fDraggingHourHand = false;
464 	fDraggingMinuteHand = false;
465 	fClock->SetMinuteDragging(false);
466 	fClock->SetHourDragging(false);
467 }
468 
469 
470 void
471 TAnalogClock::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
472 {
473 
474 	if (fDraggingMinuteHand)
475 		fClock->SetMinuteHand(point);
476 	if (fDraggingHourHand)
477 		fClock->SetHourHand(point);
478 
479 	Invalidate();
480 }
481 
482 
483 void
484 TAnalogClock::Draw(BRect /*updateRect*/)
485 {
486 	if (fBitmap) {
487 		if (fClock->IsDirty())
488 			fClock->DrawClock();
489 		DrawBitmap(fBitmap, BPoint(0, 0));
490 	}
491 }
492 
493 
494 void
495 TAnalogClock::SetTime(int32 hour, int32 minute, int32 second)
496 {
497 	// don't set the time if the hands are in a drag action
498 	if (fDraggingHourHand || fDraggingMinuteHand || fTimeChangeIsOngoing)
499 		return;
500 
501 	if (fClock)
502 		fClock->SetTime(hour, minute, second);
503 
504 	Invalidate();
505 }
506 
507 
508 void
509 TAnalogClock::ChangeTimeFinished()
510 {
511 	fTimeChangeIsOngoing = false;
512 }
513