* Copyright 2004-2011, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* McCall <mccall@@digitalparadise.co.uk>
* Mike Berg <mike@berg-net.us>
* Julun <host.haiku@gmx.de>
* Clemens <mail@Clemens-Zeidler.de>
* Adrien Destugues <pulkomandy@pulkomandy.cx>
* Hamish Morrison <hamish@lavabit.com>
*/
#include "DateTimeEdit.h"
#include <stdlib.h>
#include <ControlLook.h>
#include <DateFormat.h>
#include <LayoutUtils.h>
#include <List.h>
#include <Locale.h>
#include <String.h>
#include <Window.h>
namespace BPrivate {
const uint32 kArrowAreaWidth = 16;
TimeEdit::TimeEdit(const char* name, uint32 sections, BMessage* message)
:
SectionEdit(name, sections, message),
fLastKeyDownTime(0),
fFields(NULL),
fFieldCount(0),
fFieldPositions(NULL),
fFieldPosCount(0)
{
InitView();
}
TimeEdit::~TimeEdit()
{
free(fFieldPositions);
free(fFields);
}
void
TimeEdit::KeyDown(const char* bytes, int32 numBytes)
{
if (IsEnabled() == false)
return;
SectionEdit::KeyDown(bytes, numBytes);
int32 number = atoi(bytes);
if (number < 0 || bytes[0] < '0')
return;
int32 section = FocusIndex();
if (section < 0 || section > 2)
return;
bigtime_t currentTime = system_time();
if (currentTime - fLastKeyDownTime < 1000000) {
int32 doubleDigit = number + fLastKeyDownInt * 10;
if (_IsValidDoubleDigit(doubleDigit))
number = doubleDigit;
fLastKeyDownTime = 0;
} else {
fLastKeyDownTime = currentTime;
fLastKeyDownInt = number;
}
fHoldValue = number;
_CheckRange();
_UpdateFields();
Invoke();
}
void
TimeEdit::InitView()
{
fTime = BDateTime::CurrentDateTime(B_LOCAL_TIME);
_UpdateFields();
}
void
TimeEdit::DrawSection(uint32 index, BRect bounds, bool hasFocus)
{
if (fFieldPositions == NULL || index * 2 + 1 >= (uint32)fFieldPosCount)
return;
if (hasFocus)
SetLowColor(mix_color(ui_color(B_CONTROL_HIGHLIGHT_COLOR),
ui_color(B_DOCUMENT_BACKGROUND_COLOR), 192));
else
SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR);
BString field;
fText.CopyCharsInto(field, fFieldPositions[index * 2],
fFieldPositions[index * 2 + 1] - fFieldPositions[index * 2]);
BPoint point(bounds.LeftBottom());
point.y -= bounds.Height() / 2.0 - 6.0;
point.x += (bounds.Width() - StringWidth(field)) / 2;
FillRect(bounds, B_SOLID_LOW);
DrawString(field, point);
}
void
TimeEdit::DrawSeparator(uint32 index, BRect bounds)
{
if (fFieldPositions == NULL || index * 2 + 2 >= (uint32)fFieldPosCount)
return;
BString field;
fText.CopyCharsInto(field, fFieldPositions[index * 2 + 1],
fFieldPositions[index * 2 + 2] - fFieldPositions[index * 2 + 1]);
BPoint point(bounds.LeftBottom());
point.y -= bounds.Height() / 2.0 - 6.0;
point.x += (bounds.Width() - StringWidth(field)) / 2;
DrawString(field, point);
}
float
TimeEdit::SeparatorWidth()
{
return 10.0f;
}
float
TimeEdit::MinSectionWidth()
{
return be_plain_font->StringWidth("00");
}
void
TimeEdit::SectionFocus(uint32 index)
{
fLastKeyDownTime = 0;
fFocus = index;
fHoldValue = _SectionValue(index);
Draw(Bounds());
}
void
TimeEdit::SetTime(int32 hour, int32 minute, int32 second)
{
if (hour == 0 && minute == 0 && second == 0)
fTime = BDateTime::CurrentDateTime(B_LOCAL_TIME);
fTime.SetTime(BTime(hour, minute, second));
if (LockLooper()) {
_UpdateFields();
UnlockLooper();
}
Invalidate(Bounds());
}
BTime
TimeEdit::GetTime()
{
return fTime.Time();
}
void
TimeEdit::DoUpPress()
{
if (fFocus == -1)
SectionFocus(0);
fHoldValue += 1;
_CheckRange();
_UpdateFields();
Invoke();
}
void
TimeEdit::DoDownPress()
{
if (fFocus == -1)
SectionFocus(0);
fHoldValue -= 1;
_CheckRange();
_UpdateFields();
Invoke();
}
void
TimeEdit::PopulateMessage(BMessage* message)
{
if (fFocus < 0 || fFocus >= fFieldCount)
return;
message->AddBool("time", true);
message->AddInt32("hour", fTime.Time().Hour());
message->AddInt32("minute", fTime.Time().Minute());
message->AddInt32("second", fTime.Time().Second());
}
void
TimeEdit::_UpdateFields()
{
time_t time = fTime.Time_t();
if (fFieldPositions != NULL) {
free(fFieldPositions);
fFieldPositions = NULL;
}
fTimeFormat.Format(fText, fFieldPositions, fFieldPosCount, time,
B_MEDIUM_TIME_FORMAT);
if (fFields != NULL) {
free(fFields);
fFields = NULL;
}
fTimeFormat.GetTimeFields(fFields, fFieldCount, B_MEDIUM_TIME_FORMAT);
}
void
TimeEdit::_CheckRange()
{
if (fFocus < 0 || fFocus >= fFieldCount)
return;
int32 value = fHoldValue;
switch (fFields[fFocus]) {
case B_DATE_ELEMENT_HOUR:
if (value > 23)
value = 0;
else if (value < 0)
value = 23;
fTime.SetTime(BTime(value, fTime.Time().Minute(),
fTime.Time().Second()));
break;
case B_DATE_ELEMENT_MINUTE:
if (value> 59)
value = 0;
else if (value < 0)
value = 59;
fTime.SetTime(BTime(fTime.Time().Hour(), value,
fTime.Time().Second()));
break;
case B_DATE_ELEMENT_SECOND:
if (value > 59)
value = 0;
else if (value < 0)
value = 59;
fTime.SetTime(BTime(fTime.Time().Hour(), fTime.Time().Minute(),
value));
break;
case B_DATE_ELEMENT_AM_PM:
value = fTime.Time().Hour();
if (value < 13)
value += 12;
else
value -= 12;
if (value == 24)
value = 0;
fTime.SetTime(BTime(value, fTime.Time().Minute(),
fTime.Time().Second()));
break;
default:
return;
}
fHoldValue = value;
Invalidate(Bounds());
}
bool
TimeEdit::_IsValidDoubleDigit(int32 value)
{
if (fFocus < 0 || fFocus >= fFieldCount)
return false;
bool isInRange = false;
switch (fFields[fFocus]) {
case B_DATE_ELEMENT_HOUR:
if (value <= 23)
isInRange = true;
break;
case B_DATE_ELEMENT_MINUTE:
if (value <= 59)
isInRange = true;
break;
case B_DATE_ELEMENT_SECOND:
if (value <= 59)
isInRange = true;
break;
default:
break;
}
return isInRange;
}
int32
TimeEdit::_SectionValue(int32 index) const
{
if (index < 0 || index >= fFieldCount)
return 0;
int32 value;
switch (fFields[index]) {
case B_DATE_ELEMENT_HOUR:
value = fTime.Time().Hour();
break;
case B_DATE_ELEMENT_MINUTE:
value = fTime.Time().Minute();
break;
case B_DATE_ELEMENT_SECOND:
value = fTime.Time().Second();
break;
default:
value = 0;
break;
}
return value;
}
float
TimeEdit::PreferredHeight()
{
font_height fontHeight;
GetFontHeight(&fontHeight);
return ceilf((fontHeight.ascent + fontHeight.descent) * 1.4);
}
DateEdit::DateEdit(const char* name, uint32 sections, BMessage* message)
:
SectionEdit(name, sections, message),
fFields(NULL),
fFieldCount(0),
fFieldPositions(NULL),
fFieldPosCount(0)
{
InitView();
}
DateEdit::~DateEdit()
{
free(fFieldPositions);
free(fFields);
}
void
DateEdit::KeyDown(const char* bytes, int32 numBytes)
{
if (IsEnabled() == false)
return;
SectionEdit::KeyDown(bytes, numBytes);
int32 number = atoi(bytes);
if (number < 0 || bytes[0] < '0')
return;
int32 section = FocusIndex();
if (section < 0 || section > 2)
return;
bigtime_t currentTime = system_time();
if (currentTime - fLastKeyDownTime < 1000000) {
int32 doubleDigit = number + fLastKeyDownInt * 10;
if (_IsValidDoubleDigit(doubleDigit))
number = doubleDigit;
fLastKeyDownTime = 0;
} else {
fLastKeyDownTime = currentTime;
fLastKeyDownInt = number;
}
if (fFields[section] == B_DATE_ELEMENT_YEAR) {
int32 oldCentury = int32(fHoldValue / 100) * 100;
if (number < 10 && oldCentury == 1900)
number += 70;
number += oldCentury;
}
fHoldValue = number;
_CheckRange();
_UpdateFields();
Invoke();
}
void
DateEdit::InitView()
{
fDate = BDate::CurrentDate(B_LOCAL_TIME);
_UpdateFields();
}
void
DateEdit::DrawSection(uint32 index, BRect bounds, bool hasFocus)
{
if (fFieldPositions == NULL || index * 2 + 1 >= (uint32)fFieldPosCount)
return;
if (hasFocus)
SetLowColor(mix_color(ui_color(B_CONTROL_HIGHLIGHT_COLOR),
ui_color(B_DOCUMENT_BACKGROUND_COLOR), 192));
else
SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR);
BString field;
fText.CopyCharsInto(field, fFieldPositions[index * 2],
fFieldPositions[index * 2 + 1] - fFieldPositions[index * 2]);
BPoint point(bounds.LeftBottom());
point.y -= bounds.Height() / 2.0 - 6.0;
point.x += (bounds.Width() - StringWidth(field)) / 2;
FillRect(bounds, B_SOLID_LOW);
DrawString(field, point);
}
void
DateEdit::DrawSeparator(uint32 index, BRect bounds)
{
if (index >= 2)
return;
if (fFieldPositions == NULL || index * 2 + 2 >= (uint32)fFieldPosCount)
return;
BString field;
fText.CopyCharsInto(field, fFieldPositions[index * 2 + 1],
fFieldPositions[index * 2 + 2] - fFieldPositions[index * 2 + 1]);
BPoint point(bounds.LeftBottom());
point.y -= bounds.Height() / 2.0 - 6.0;
point.x += (bounds.Width() - StringWidth(field)) / 2;
DrawString(field, point);
}
void
DateEdit::SectionFocus(uint32 index)
{
fLastKeyDownTime = 0;
fFocus = index;
fHoldValue = _SectionValue(index);
Draw(Bounds());
}
float
DateEdit::MinSectionWidth()
{
return be_plain_font->StringWidth("00");
}
float
DateEdit::SeparatorWidth()
{
return 10.0f;
}
void
DateEdit::SetDate(int32 year, int32 month, int32 day)
{
fDate.SetDate(year, month, day);
if (LockLooper()) {
_UpdateFields();
UnlockLooper();
}
Invalidate(Bounds());
}
BDate
DateEdit::GetDate()
{
return fDate;
}
void
DateEdit::DoUpPress()
{
if (fFocus == -1)
SectionFocus(0);
fHoldValue += 1;
_CheckRange();
_UpdateFields();
Invoke();
}
void
DateEdit::DoDownPress()
{
if (fFocus == -1)
SectionFocus(0);
fHoldValue -= 1;
_CheckRange();
_UpdateFields();
Invoke();
}
void
DateEdit::PopulateMessage(BMessage* message)
{
if (fFocus < 0 || fFocus >= fFieldCount)
return;
message->AddBool("time", false);
message->AddInt32("year", fDate.Year());
message->AddInt32("month", fDate.Month());
message->AddInt32("day", fDate.Day());
}
void
DateEdit::_UpdateFields()
{
time_t time = BDateTime(fDate, BTime()).Time_t();
if (fFieldPositions != NULL) {
free(fFieldPositions);
fFieldPositions = NULL;
}
fDateFormat.Format(fText, fFieldPositions, fFieldPosCount, time,
B_SHORT_DATE_FORMAT);
if (fFields != NULL) {
free(fFields);
fFields = NULL;
}
fDateFormat.GetFields(fFields, fFieldCount, B_SHORT_DATE_FORMAT);
}
void
DateEdit::_CheckRange()
{
if (fFocus < 0 || fFocus >= fFieldCount)
return;
int32 value = fHoldValue;
switch (fFields[fFocus]) {
case B_DATE_ELEMENT_DAY:
{
int32 days = fDate.DaysInMonth();
if (value > days)
value = 1;
else if (value < 1)
value = days;
fDate.SetDate(fDate.Year(), fDate.Month(), value);
break;
}
case B_DATE_ELEMENT_MONTH:
{
if (value > 12)
value = 1;
else if (value < 1)
value = 12;
int32 day = fDate.Day();
fDate.SetDate(fDate.Year(), value, 1);
while (day > fDate.DaysInMonth())
day--;
fDate.SetDate(fDate.Year(), value, day);
break;
}
case B_DATE_ELEMENT_YEAR:
fDate.SetDate(value, fDate.Month(), fDate.Day());
break;
default:
return;
}
fHoldValue = value;
Invalidate(Bounds());
}
bool
DateEdit::_IsValidDoubleDigit(int32 value)
{
if (fFocus < 0 || fFocus >= fFieldCount)
return false;
bool isInRange = false;
switch (fFields[fFocus]) {
case B_DATE_ELEMENT_DAY:
{
int32 days = fDate.DaysInMonth();
if (value >= 1 && value <= days)
isInRange = true;
break;
}
case B_DATE_ELEMENT_MONTH:
{
if (value >= 1 && value <= 12)
isInRange = true;
break;
}
case B_DATE_ELEMENT_YEAR:
{
int32 year = int32(fHoldValue / 100) * 100 + value;
if (year >= 2000)
isInRange = true;
break;
}
default:
break;
}
return isInRange;
}
int32
DateEdit::_SectionValue(int32 index) const
{
if (index < 0 || index >= fFieldCount)
return 0;
int32 value = 0;
switch (fFields[index]) {
case B_DATE_ELEMENT_YEAR:
value = fDate.Year();
break;
case B_DATE_ELEMENT_MONTH:
value = fDate.Month();
break;
case B_DATE_ELEMENT_DAY:
value = fDate.Day();
break;
default:
break;
}
return value;
}
float
DateEdit::PreferredHeight()
{
font_height fontHeight;
GetFontHeight(&fontHeight);
return ceilf((fontHeight.ascent + fontHeight.descent) * 1.4);
}
SectionEdit::SectionEdit(const char* name, uint32 sections, BMessage* message)
:
BControl(name, NULL, message, B_WILL_DRAW | B_NAVIGABLE),
fFocus(-1),
fSectionCount(sections),
fHoldValue(0)
{
}
SectionEdit::~SectionEdit()
{
}
void
SectionEdit::AttachedToWindow()
{
BControl::AttachedToWindow();
SetHighUIColor(B_DOCUMENT_TEXT_COLOR);
}
void
SectionEdit::Draw(BRect updateRect)
{
DrawBorder(updateRect);
for (uint32 idx = 0; idx < fSectionCount; idx++) {
DrawSection(idx, FrameForSection(idx),
((uint32)fFocus == idx) && IsFocus());
if (idx < fSectionCount - 1)
DrawSeparator(idx, FrameForSeparator(idx));
}
}
void
SectionEdit::MouseDown(BPoint where)
{
if (IsEnabled() == false)
return;
MakeFocus(true);
if (fUpRect.Contains(where))
DoUpPress();
else if (fDownRect.Contains(where))
DoDownPress();
else if (fSectionCount > 0) {
for (uint32 idx = 0; idx < fSectionCount; idx++) {
if (FrameForSection(idx).Contains(where)) {
SectionFocus(idx);
return;
}
}
}
}
BSize
SectionEdit::MaxSize()
{
return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
BSize(B_SIZE_UNLIMITED, PreferredHeight()));
}
BSize
SectionEdit::MinSize()
{
BSize minSize;
minSize.height = PreferredHeight();
minSize.width = (SeparatorWidth() + MinSectionWidth())
* fSectionCount;
return BLayoutUtils::ComposeSize(ExplicitMinSize(),
minSize);
}
BSize
SectionEdit::PreferredSize()
{
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
MinSize());
}
BRect
SectionEdit::FrameForSection(uint32 index)
{
BRect area = SectionArea();
float sepWidth = SeparatorWidth();
float width = (area.Width() -
sepWidth * (fSectionCount - 1))
/ fSectionCount;
area.left += index * (width + sepWidth);
area.right = area.left + width;
return area;
}
BRect
SectionEdit::FrameForSeparator(uint32 index)
{
BRect area = SectionArea();
float sepWidth = SeparatorWidth();
float width = (area.Width() -
sepWidth * (fSectionCount - 1))
/ fSectionCount;
area.left += (index + 1) * width + index * sepWidth;
area.right = area.left + sepWidth;
return area;
}
void
SectionEdit::MakeFocus(bool focused)
{
if (focused == IsFocus())
return;
BControl::MakeFocus(focused);
if (fFocus == -1)
SectionFocus(0);
else
SectionFocus(fFocus);
}
void
SectionEdit::KeyDown(const char* bytes, int32 numbytes)
{
if (IsEnabled() == false)
return;
if (fFocus == -1)
SectionFocus(0);
switch (bytes[0]) {
case B_LEFT_ARROW:
fFocus -= 1;
if (fFocus < 0)
fFocus = fSectionCount - 1;
SectionFocus(fFocus);
break;
case B_RIGHT_ARROW:
fFocus += 1;
if ((uint32)fFocus >= fSectionCount)
fFocus = 0;
SectionFocus(fFocus);
break;
case B_UP_ARROW:
DoUpPress();
break;
case B_DOWN_ARROW:
DoDownPress();
break;
default:
BControl::KeyDown(bytes, numbytes);
break;
}
Draw(Bounds());
}
status_t
SectionEdit::Invoke(BMessage* message)
{
if (message == NULL)
message = Message();
if (message == NULL)
return BControl::Invoke(NULL);
BMessage clone(*message);
PopulateMessage(&clone);
return BControl::Invoke(&clone);
}
uint32
SectionEdit::CountSections() const
{
return fSectionCount;
}
int32
SectionEdit::FocusIndex() const
{
return fFocus;
}
BRect
SectionEdit::SectionArea() const
{
BRect sectionArea = Bounds().InsetByCopy(2, 2);
sectionArea.right -= kArrowAreaWidth;
return sectionArea;
}
void
SectionEdit::DrawBorder(const BRect& updateRect)
{
BRect bounds(Bounds());
bool showFocus = (IsFocus() && Window() && Window()->IsActive());
be_control_look->DrawTextControlBorder(this, bounds, updateRect, ViewColor(),
showFocus ? BControlLook::B_FOCUSED : 0);
SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR);
FillRect(bounds, B_SOLID_LOW);
bounds.left = bounds.right - kArrowAreaWidth;
bounds.right = Bounds().right - 2;
fUpRect.Set(bounds.left + 3, bounds.top + 2, bounds.right,
bounds.bottom / 2.0);
fDownRect = fUpRect.OffsetByCopy(0, fUpRect.Height() + 2);
BPoint middle(floorf(fUpRect.left + fUpRect.Width() / 2),
fUpRect.top + 1);
BPoint left(fUpRect.left + 3, fUpRect.bottom - 1);
BPoint right(left.x + 2 * (middle.x - left.x), fUpRect.bottom - 1);
SetPenSize(2);
if (updateRect.Intersects(fUpRect)) {
FillRect(fUpRect, B_SOLID_LOW);
BeginLineArray(2);
AddLine(left, middle, HighColor());
AddLine(middle, right, HighColor());
EndLineArray();
}
if (updateRect.Intersects(fDownRect)) {
middle.y = fDownRect.bottom - 1;
left.y = right.y = fDownRect.top + 1;
FillRect(fDownRect, B_SOLID_LOW);
BeginLineArray(2);
AddLine(left, middle, HighColor());
AddLine(middle, right, HighColor());
EndLineArray();
}
SetPenSize(1);
}
}