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