patch by Clemens Zeidler:

* Support dragging the clock hour and minute hands to set the time.
* Improved keyboard support for the time and date controls.

Thanks a lot!


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@28713 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stephan Aßmus 2008-11-22 00:23:51 +00:00
parent e11c40c4e2
commit 3e58fe9e70
8 changed files with 373 additions and 31 deletions

View File

@ -6,14 +6,20 @@
* Mike Berg <mike@berg-net.us>
* Julun <host.haiku@gmx.de>
* Stephan Aßmus <superstippi@gmx.de>
* Clemens <mail@Clemens-Zeidler.de>
*/
#include "AnalogClock.h"
#include "TimeMessages.h"
#include <Bitmap.h>
#include <Message.h>
#include <Window.h>
#include <cmath>
#include <stdio.h>
#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;
}

View File

@ -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

View File

@ -6,6 +6,7 @@
* McCall <mccall@@digitalparadise.co.uk>
* Mike Berg <mike@berg-net.us>
* Julun <host.haiku@gmx.de>
* Clemens <mail@Clemens-Zeidler.de>
*
*/
@ -14,13 +15,15 @@
#include <List.h>
#include <String.h>
#include <Window.h>
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());

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);
}

View File

@ -33,6 +33,8 @@ class TTimeWindow : public BWindow {
void _InitWindow();
void _AlignWindow();
void _SendTimeChangeFinished();
private:
TTimeBaseView *fBaseView;
DateTimeView *fDateTimeView;