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