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