diff --git a/src/preferences/time/AnalogClock.cpp b/src/preferences/time/AnalogClock.cpp index 640785d055..5982b86760 100644 --- a/src/preferences/time/AnalogClock.cpp +++ b/src/preferences/time/AnalogClock.cpp @@ -6,14 +6,20 @@ * Mike Berg * Julun * Stephan Aßmus + * Clemens */ #include "AnalogClock.h" #include "TimeMessages.h" - #include #include +#include + +#include +#include + +#define DRAG_DELTA_PHI 0.2 class OffscreenClock : public BView { @@ -22,17 +28,44 @@ class OffscreenClock : public BView { ~OffscreenClock(); void SetTime(int32 hour, int32 minute, int32 second); + void GetTime(int32 *hour, int32 *minute, int32 *second); bool IsDirty() const { return fDirty; } void DrawClock(); + bool InHourHand(BPoint point); + bool InMinuteHand(BPoint point); + + void SetHourHand(BPoint point); + void SetMinuteHand(BPoint point); + + void SetHourDragging(bool val) { + fHourDragging = val; + fDirty = true; + } + void SetMinuteDragging(bool val) { + fMinuteDragging = val; + fDirty = true; + } private: + float _GetPhi(BPoint point); + bool _InHand(BPoint point, int32 ticks, float radius); void _DrawHands(float x, float y, float radius, - rgb_color hourMinuteColor, rgb_color secondsColor); + rgb_color hourHourColor, + rgb_color hourMinuteColor, + rgb_color secondsColor, + rgb_color knobColor); int32 fHours; int32 fMinutes; int32 fSeconds; bool fDirty; + + float fCenterX; + float fCenterY; + float fRadius; + + bool fHourDragging; + bool fMinuteDragging; }; @@ -41,9 +74,20 @@ OffscreenClock::OffscreenClock(BRect frame, const char *name) fHours(0), fMinutes(0), fSeconds(0), - fDirty(true) + fDirty(true), + fHourDragging(false), + fMinuteDragging(false) { SetFlags(Flags() | B_SUBPIXEL_PRECISE); + + BRect bounds = Bounds(); + fCenterX = floorf((bounds.left + bounds.right) / 2 + 0.5) + 0.5; + fCenterY = floorf((bounds.top + bounds.bottom) / 2 + 0.5) + 0.5; + // + 0.5 is for the offset to pixel centers + // (important when drawing with B_SUBPIXEL_PRECISE) + + fRadius = floorf((MIN(bounds.Width(), bounds.Height()) / 2.0)) - 2.5; + fRadius -= 3; } @@ -66,6 +110,15 @@ OffscreenClock::SetTime(int32 hour, int32 minute, int32 second) } +void +OffscreenClock::GetTime(int32 *hour, int32 *minute, int32 *second) +{ + *hour = fHours; + *minute = fMinutes; + *second = fSeconds; +} + + void OffscreenClock::DrawClock() { @@ -78,13 +131,9 @@ OffscreenClock::DrawClock() SetHighColor(background); FillRect(bounds); - float radius = floorf((MIN(bounds.Width(), bounds.Height()) / 2.0)) - 2.5; - float x = floorf((bounds.left + bounds.right) / 2 + 0.5) + 0.5; - float y = floorf((bounds.top + bounds.bottom) / 2 + 0.5) + 0.5; - // + 0.5 is for the offset to pixel centers - // (important when drawing with B_SUBPIXEL_PRECISE) - - bounds.Set(x - radius, y - radius, x + radius, y + radius); + + bounds.Set(fCenterX - fRadius, fCenterY - fRadius, + fCenterX + fRadius, fCenterY + fRadius); SetPenSize(2.0); @@ -99,8 +148,7 @@ OffscreenClock::DrawClock() SetLowColor(255, 255, 255); FillEllipse(bounds, B_SOLID_LOW); - radius -= 3; - + SetHighColor(tint_color(HighColor(), B_DARKEN_2_TINT)); // minutes @@ -109,10 +157,10 @@ OffscreenClock::DrawClock() for (int32 minute = 1; minute < 60; minute++) { if (minute % 5 == 0) continue; - float x1 = x + sinf(minute * PI / 30.0) * radius; - float y1 = y + cosf(minute * PI / 30.0) * radius; - float x2 = x + sinf(minute * PI / 30.0) * (radius * 0.95); - float y2 = y + cosf(minute * PI / 30.0) * (radius * 0.95); + float x1 = fCenterX + sinf(minute * PI / 30.0) * fRadius; + float y1 = fCenterY + cosf(minute * PI / 30.0) * fRadius; + float x2 = fCenterX + sinf(minute * PI / 30.0) * (fRadius * 0.95); + float y2 = fCenterY + cosf(minute * PI / 30.0) * (fRadius * 0.95); StrokeLine(BPoint(x1, y1), BPoint(x2, y2)); } @@ -122,20 +170,32 @@ OffscreenClock::DrawClock() SetPenSize(2.0); SetLineMode(B_ROUND_CAP, B_MITER_JOIN); for (int32 hour = 0; hour < 12; hour++) { - float x1 = x + sinf(hour * PI / 6.0) * radius; - float y1 = y + cosf(hour * PI / 6.0) * radius; - float x2 = x + sinf(hour * PI / 6.0) * (radius * 0.9); - float y2 = y + cosf(hour * PI / 6.0) * (radius * 0.9); + float x1 = fCenterX + sinf(hour * PI / 6.0) * fRadius; + float y1 = fCenterY + cosf(hour * PI / 6.0) * fRadius; + float x2 = fCenterX + sinf(hour * PI / 6.0) * (fRadius * 0.9); + float y2 = fCenterY + cosf(hour * PI / 6.0) * (fRadius * 0.9); StrokeLine(BPoint(x1, y1), BPoint(x2, y2)); } - rgb_color hourMinutColor = tint_color(HighColor(), B_DARKEN_2_TINT); + rgb_color knobColor = tint_color(HighColor(), B_DARKEN_2_TINT);; + rgb_color hourColor; + if (fHourDragging) + hourColor = (rgb_color){ 0, 0, 255, 255 }; + else + hourColor = tint_color(HighColor(), B_DARKEN_2_TINT); + rgb_color minuteColor; + if (fMinuteDragging) + minuteColor = (rgb_color){ 0, 0, 255, 255 }; + else + minuteColor = tint_color(HighColor(), B_DARKEN_2_TINT); rgb_color secondsColor = (rgb_color){ 255, 0, 0, 255 }; rgb_color shadowColor = tint_color(LowColor(), (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2); - _DrawHands(x + 1.5, y + 1.5, radius, shadowColor, shadowColor); - _DrawHands(x, y, radius, hourMinutColor, secondsColor); + _DrawHands(fCenterX + 1.5, fCenterY + 1.5, fRadius, + shadowColor, shadowColor, shadowColor, knobColor); + _DrawHands(fCenterX, fCenterY, fRadius, + hourColor, minuteColor, secondsColor, knobColor); Sync(); @@ -143,16 +203,116 @@ OffscreenClock::DrawClock() } +bool +OffscreenClock::InHourHand(BPoint point) +{ + int32 ticks = fHours; + if (ticks > 12) + ticks -= 12; + ticks *= 5; + ticks += int32(5. * fMinutes / 60.0); + if (ticks > 60) + ticks -= 60; + return _InHand(point, ticks, fRadius * 0.7); +} + + +bool +OffscreenClock::InMinuteHand(BPoint point) +{ + return _InHand(point, fMinutes, fRadius * 0.9); +} + + +void +OffscreenClock::SetHourHand(BPoint point) +{ + point.x -= fCenterX; + point.y -= fCenterY; + + float pointPhi = _GetPhi(point); + float hoursExact = 6.0 * pointPhi / PI; + if (fHours >= 12) + fHours = 12; + else + fHours = 0; + fHours += int32(hoursExact); + + SetTime(fHours, fMinutes, fSeconds); +} + + +void +OffscreenClock::SetMinuteHand(BPoint point) +{ + point.x -= fCenterX; + point.y -= fCenterY; + + float pointPhi = _GetPhi(point); + float minutesExact = 30.0 * pointPhi / PI; + fMinutes = int32(ceilf(minutesExact)); + + SetTime(fHours, fMinutes, fSeconds); +} + + +float +OffscreenClock::_GetPhi(BPoint point) +{ + if (point.x == 0 && point.y < 0) + return 2 * PI; + if (point.x == 0 && point.y > 0) + return PI; + if (point.y == 0 && point.x < 0) + return PI * 3 / 2; + if (point.y == 0 && point.x > 0) + return PI / 2; + + float pointPhi = atanf(-1. * point.y / point.x); + if (point.y < 0. && point.x > 0.) // right upper corner + pointPhi = PI / 2. - pointPhi; + if (point.y > 0. && point.x > 0.) // right lower corner + pointPhi = PI / 2 - pointPhi; + if (point.y > 0. && point.x < 0.) // left lower corner + pointPhi = (PI * 3. / 2. - pointPhi); + if (point.y < 0. && point.x < 0.) // left upper corner + pointPhi = 3. / 2. * PI - pointPhi; + return pointPhi; +} + +bool +OffscreenClock::_InHand(BPoint point, int32 ticks, float radius) +{ + point.x -= fCenterX; + point.y -= fCenterY; + + float pRadius = sqrt(pow(point.x, 2) + pow(point.y, 2)); + + if (radius < pRadius) + return false; + + float pointPhi = _GetPhi(point); + float handPhi = PI / 30.0 * ticks; + float delta = pointPhi - handPhi; + if (abs(delta) > DRAG_DELTA_PHI) + return false; + + return true; +} + + void OffscreenClock::_DrawHands(float x, float y, float radius, - rgb_color hourMinuteColor, rgb_color secondsColor) + rgb_color hourColor, + rgb_color minuteColor, + rgb_color secondsColor, + rgb_color knobColor) { - SetHighColor(hourMinuteColor); - float offsetX; float offsetY; // calc, draw hour hand + SetHighColor(hourColor); SetPenSize(4.0); float hours = fHours + float(fMinutes) / 60.0; offsetX = (radius * 0.7) * sinf((hours * PI) / 6.0); @@ -160,22 +320,22 @@ OffscreenClock::_DrawHands(float x, float y, float radius, StrokeLine(BPoint(x, y), BPoint(x + offsetX, y - offsetY)); // calc, draw minute hand + SetHighColor(minuteColor); SetPenSize(3.0); float minutes = fMinutes + float(fSeconds) / 60.0; offsetX = (radius * 0.9) * sinf((minutes * PI) / 30.0); offsetY = (radius * 0.9) * cosf((minutes * PI) / 30.0); StrokeLine(BPoint(x, y), BPoint(x + offsetX, y - offsetY)); - SetHighColor(secondsColor); - // calc, draw second hand + SetHighColor(secondsColor); SetPenSize(1.0); offsetX = (radius * 0.95) * sinf((fSeconds * PI) / 30.0); offsetY = (radius * 0.95) * cosf((fSeconds * PI) / 30.0); StrokeLine(BPoint(x, y), BPoint(x + offsetX, y - offsetY)); // draw the center knob - SetHighColor(hourMinuteColor); + SetHighColor(knobColor); FillEllipse(BPoint(x, y), radius * 0.06, radius * 0.06); } @@ -186,7 +346,10 @@ OffscreenClock::_DrawHands(float x, float y, float radius, TAnalogClock::TAnalogClock(BRect frame, const char *name) : BView(frame, name, B_FOLLOW_NONE, B_WILL_DRAW | B_DRAW_ON_CHILDREN), fBitmap(NULL), - fClock(NULL) + fClock(NULL), + fDraggingHourHand(false), + fDraggingMinuteHand(false), + fTimeChangeIsOngoing(false) { _InitView(frame); } @@ -247,6 +410,58 @@ TAnalogClock::MessageReceived(BMessage *message) } +void +TAnalogClock::MouseDown(BPoint point) +{ + fDraggingMinuteHand = fClock->InMinuteHand(point); + if (fDraggingMinuteHand) { + fClock->SetMinuteDragging(true); + SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); + Invalidate(); + return; + } + fDraggingHourHand = fClock->InHourHand(point); + if (fDraggingHourHand) { + fClock->SetHourDragging(true); + SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); + Invalidate(); + return; + } +} + +void +TAnalogClock::MouseUp(BPoint point) +{ + if (fDraggingHourHand || fDraggingMinuteHand) { + int32 hour, minute, second; + fClock->GetTime(&hour, &minute, &second); + BMessage message(H_USER_CHANGE); + message.AddBool("time", true); + message.AddInt32("hour", hour); + message.AddInt32("minute", minute); + Window()->PostMessage(&message); + fTimeChangeIsOngoing = true; + } + fDraggingHourHand = false; + fDraggingMinuteHand = false; + fClock->SetMinuteDragging(false); + fClock->SetHourDragging(false); +} + + +void +TAnalogClock::MouseMoved(BPoint point, uint32 transit, const BMessage *message) +{ + + if (fDraggingMinuteHand) + fClock->SetMinuteHand(point); + if (fDraggingHourHand) + fClock->SetHourHand(point); + + Invalidate(); +} + + void TAnalogClock::Draw(BRect /*updateRect*/) { @@ -261,9 +476,19 @@ TAnalogClock::Draw(BRect /*updateRect*/) void TAnalogClock::SetTime(int32 hour, int32 minute, int32 second) { + // don't set the time if the hands are in a drag action + if (fDraggingHourHand || fDraggingMinuteHand || fTimeChangeIsOngoing) + return; + if (fClock) fClock->SetTime(hour, minute, second); Invalidate(); } + +void +TAnalogClock::ChangeTimeFinished() +{ + fTimeChangeIsOngoing = false; +} diff --git a/src/preferences/time/AnalogClock.h b/src/preferences/time/AnalogClock.h index 2fffe153a4..ca2f4c5ecf 100644 --- a/src/preferences/time/AnalogClock.h +++ b/src/preferences/time/AnalogClock.h @@ -25,14 +25,24 @@ class TAnalogClock : public BView { virtual void AttachedToWindow(); virtual void Draw(BRect updateRect); virtual void MessageReceived(BMessage *message); + virtual void MouseDown(BPoint point); + virtual void MouseUp(BPoint point); + virtual void MouseMoved(BPoint point, uint32 transit, const BMessage *message); void SetTime(int32 hour, int32 minute, int32 second); + bool IsChangingTime() { return fTimeChangeIsOngoing; } + void ChangeTimeFinished(); private: void _InitView(BRect frame); BBitmap *fBitmap; OffscreenClock *fClock; + + bool fDraggingHourHand; + bool fDraggingMinuteHand; + + bool fTimeChangeIsOngoing; }; #endif // ANALOG_CLOCK_H diff --git a/src/preferences/time/DateTimeEdit.cpp b/src/preferences/time/DateTimeEdit.cpp index 943f0ebd24..da20e4e05e 100644 --- a/src/preferences/time/DateTimeEdit.cpp +++ b/src/preferences/time/DateTimeEdit.cpp @@ -6,6 +6,7 @@ * McCall * Mike Berg * Julun + * Clemens * */ @@ -14,13 +15,15 @@ #include #include +#include using BPrivate::B_LOCAL_TIME; TTimeEdit::TTimeEdit(BRect frame, const char *name, uint32 sections) - : TSectionEdit(frame, name, sections) + : TSectionEdit(frame, name, sections), + fLastKeyDownTime(0) { InitView(); fTime = BTime::CurrentTime(B_LOCAL_TIME); @@ -32,6 +35,42 @@ TTimeEdit::~TTimeEdit() } +void +TTimeEdit::KeyDown(const char* bytes, int32 numBytes) +{ + TSectionEdit::KeyDown(bytes, numBytes); + + BMessage* keyDownMsg = Window()->CurrentMessage(); + int32 number; + keyDownMsg->FindInt32("raw_char", &number); + // only accept int + if (number < 48 || number > 57) + return; + number -= 48; + int32 section = FocusIndex(); + if (section < 0 || section > 2) + return; + + bigtime_t currentTime = system_time(); + if (currentTime - fLastKeyDownTime < 1000000) { + number += fLastKeyDownInt * 10; + fLastKeyDownTime = 0; + } + else { + fLastKeyDownTime = currentTime; + fLastKeyDownInt = number; + } + + // update display value + fHoldValue = number; + + _CheckRange(); + + // send message to change time + DispatchMessage(); +} + + void TTimeEdit::InitView() { @@ -173,6 +212,7 @@ TTimeEdit::SeperatorWidth() const void TTimeEdit::SectionFocus(uint32 index) { + fLastKeyDownTime = 0; fFocus = index; fHoldValue = _SectionValue(index); Draw(Bounds()); @@ -341,6 +381,45 @@ TDateEdit::~TDateEdit() } +void +TDateEdit::KeyDown(const char* bytes, int32 numBytes) +{ + TSectionEdit::KeyDown(bytes, numBytes); + + BMessage* keyDownMsg = Window()->CurrentMessage(); + int32 number; + keyDownMsg->FindInt32("raw_char", &number); + // only accept int + if (number < 48 || number > 57) + return; + number -= 48; + int32 section = FocusIndex(); + if (section < 1 || section > 2) + return; + + bigtime_t currentTime = system_time(); + if (currentTime - fLastKeyDownTime < 1000000) { + number += fLastKeyDownInt * 10; + fLastKeyDownTime = 0; + } + else { + fLastKeyDownTime = currentTime; + fLastKeyDownInt = number; + } + + // if year add 2000 + if (section == 2) + number += 2000; + // update display value + fHoldValue = number; + + _CheckRange(); + + // send message to change time + DispatchMessage(); +} + + void TDateEdit::InitView() { @@ -452,6 +531,7 @@ TDateEdit::SeperatorWidth() const void TDateEdit::SectionFocus(uint32 index) { + fLastKeyDownTime = 0; fFocus = index; fHoldValue = _SectionValue(index); Draw(Bounds()); diff --git a/src/preferences/time/DateTimeEdit.h b/src/preferences/time/DateTimeEdit.h index b765f1f616..804953740e 100644 --- a/src/preferences/time/DateTimeEdit.h +++ b/src/preferences/time/DateTimeEdit.h @@ -26,6 +26,7 @@ class TTimeEdit : public TSectionEdit { public: TTimeEdit(BRect frame, const char *name, uint32 sections); virtual ~TTimeEdit(); + virtual void KeyDown(const char* bytes, int32 numBytes); virtual void InitView(); virtual void DrawSection(uint32 index, bool isfocus); @@ -48,6 +49,8 @@ class TTimeEdit : public TSectionEdit { private: BTime fTime; + bigtime_t fLastKeyDownTime; + int32 fLastKeyDownInt; }; @@ -55,6 +58,7 @@ class TDateEdit : public TSectionEdit { public: TDateEdit(BRect frame, const char *name, uint32 sections); virtual ~TDateEdit(); + virtual void KeyDown(const char* bytes, int32 numBytes); virtual void InitView(); virtual void DrawSection(uint32 index, bool isfocus); @@ -77,6 +81,8 @@ class TDateEdit : public TSectionEdit { private: BDate fDate; + bigtime_t fLastKeyDownTime; + int32 fLastKeyDownInt; }; #endif // DATETIME_H diff --git a/src/preferences/time/DateTimeView.cpp b/src/preferences/time/DateTimeView.cpp index 79ca53bd6a..de8b105fb3 100644 --- a/src/preferences/time/DateTimeView.cpp +++ b/src/preferences/time/DateTimeView.cpp @@ -139,6 +139,11 @@ DateTimeView::MessageReceived(BMessage *message) _Revert(); break; + case kChangeTimeFinished: + if (fClock->IsChangingTime()) + fTimeEdit->MakeFocus(false); + fClock->ChangeTimeFinished(); + break; default: BView::MessageReceived(message); break; diff --git a/src/preferences/time/TimeMessages.h b/src/preferences/time/TimeMessages.h index a82ec16034..ee6c286c7f 100644 --- a/src/preferences/time/TimeMessages.h +++ b/src/preferences/time/TimeMessages.h @@ -44,5 +44,8 @@ const uint32 kDayChanged = '_kdc'; // clicked on revert button const uint32 kMsgRevert = 'rvrt'; +// change time finished +const uint32 kChangeTimeFinished = 'tcfi'; + #endif //TIME_MESSAGES_H diff --git a/src/preferences/time/TimeWindow.cpp b/src/preferences/time/TimeWindow.cpp index 3d6edfb56b..0300f1996a 100644 --- a/src/preferences/time/TimeWindow.cpp +++ b/src/preferences/time/TimeWindow.cpp @@ -52,6 +52,8 @@ TTimeWindow::MessageReceived(BMessage *message) switch(message->what) { case H_USER_CHANGE: fBaseView->ChangeTime(message); + // To make sure no old time message is in the queue + _SendTimeChangeFinished(); SetRevertStatus(); break; @@ -160,3 +162,12 @@ TTimeWindow::_AlignWindow() MoveTo(leftTop); } } + + +void +TTimeWindow::_SendTimeChangeFinished() +{ + BMessenger messenger(fDateTimeView); + BMessage msg(kChangeTimeFinished); + messenger.SendMessage(&msg); +} diff --git a/src/preferences/time/TimeWindow.h b/src/preferences/time/TimeWindow.h index 435d6bd3b2..462248a177 100644 --- a/src/preferences/time/TimeWindow.h +++ b/src/preferences/time/TimeWindow.h @@ -33,6 +33,8 @@ class TTimeWindow : public BWindow { void _InitWindow(); void _AlignWindow(); + void _SendTimeChangeFinished(); + private: TTimeBaseView *fBaseView; DateTimeView *fDateTimeView;