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 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 50 TAnalogClock::~TAnalogClock() 51 { 52 } 53 54 55 void 56 TAnalogClock::Draw(BRect /*updateRect*/) 57 { 58 if (fDirty) 59 DrawClock(); 60 } 61 62 63 void 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 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 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 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 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 176 TAnalogClock::MaxSize() 177 { 178 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 179 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); 180 } 181 182 183 BSize 184 TAnalogClock::MinSize() 185 { 186 return BSize(64.f, 64.f); 187 } 188 189 190 BSize 191 TAnalogClock::PreferredSize() 192 { 193 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), 194 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); 195 } 196 197 198 void 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 223 TAnalogClock::IsChangingTime() 224 { 225 return fTimeChangeIsOngoing; 226 } 227 228 229 void 230 TAnalogClock::ChangeTimeFinished() 231 { 232 fTimeChangeIsOngoing = false; 233 } 234 235 236 void 237 TAnalogClock::GetTime(int32* hour, int32* minute, int32* second) 238 { 239 *hour = fHours; 240 *minute = fMinutes; 241 *second = fSeconds; 242 } 243 244 245 void 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 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 345 TAnalogClock::InMinuteHand(BPoint point) 346 { 347 return _InHand(point, fMinutes, fRadius * 0.9); 348 } 349 350 351 void 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 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 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 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 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