// Calendar Control version 2.5
// by Al.V. Sarikov.
// Kherson, Ukraine, 2006.
// E-mail: avix@ukrpost.net.
// Home page: http://avix.pp.ru.

// Control which allows to work with dates:
// enter date to text field and choose it from calendar.

// Distributed under BSD license (see LICENSE file).

#include <Clipboard.h>
#include <InterfaceDefs.h>
#include <Rect.h>
#include <String.h>
#include <TextView.h>
#include <stdlib.h>
#include <time.h>


#define LAST_FORMAT 1 // quantity of formats - 1
#define LAST_DIVIDER 2 // quantity of dividers - 1


class DateTextView: public BTextView
{
 public:
  DateTextView(int day, int month, int year, uint32 flags, uint32 look);
  virtual void Cut(BClipboard *clip);
  virtual void KeyDown(const char *bytes, int32 numBytes);
  virtual void MakeFocus(bool focused);
  virtual void Paste(BClipboard *clip);
  virtual void SetEnabled(bool enabled);
  
  void GetDate(int *day, int *month, int *year);
  void SetDate(int day, int month, int year);
  void SetDate(const char *tdate);
  void GetYearRange(int *first_year, int *last_year);
  uint32 GetDivider();
  void SetDivider(uint32 dvder);
  uint32 GetDateFlags();
  void SetDateFlags(uint32 flags);
  
 protected:
  virtual void DeleteText(int32 start, int32 finish);
 private:
  virtual bool AcceptsDrop(const BMessage *message);
  virtual bool AcceptsPaste(BClipboard *clip);
  
  void DrawDate(int day, int month, int year);
  bool VerifyDate(int *day, int *month, int *year);
  
  bool is_ins; // is it necessar to insert symbol
  uint32 flags;
  char *div[LAST_DIVIDER+1]; // Ņstrings of dividers
  uint32 divider;
  
  bool enabled;
  
  int first_year;
  int last_year; // first and last year which control accepts
  
  int textlen; // length of text string (10 or 8 symbols)
  
  uint32 interface; // the same as control variable
};


//////////////////////////////////////////////////////////////////////////
DateTextView::DateTextView(int day, int month, int year,
                           uint32 flags, uint32 look)
             :BTextView(BRect(0,0,110,15),"DateTextViewAViX",
                        BRect(0,0,110,15),B_FOLLOW_LEFT | B_FOLLOW_TOP,
                        B_WILL_DRAW | B_NAVIGABLE)
{
 enabled=true;
 
 is_ins=false;
 
 interface=look;
 
 div[0]=(char*)".";
 div[1]=(char*)"/";
 div[2]=(char*)"-";
 
 divider=look & CC_ALL_DIVIDERS;
 if(divider>LAST_DIVIDER || divider<0) divider=0; // index of divider
 
 this->flags=(flags & (LAST_FORMAT | CC_SHORT_YEAR | CC_HALF_CENTURY));
 if((this->flags & (CC_SHORT_YEAR | CC_HALF_CENTURY))==CC_HALF_CENTURY)
  this->flags=this->flags^CC_HALF_CENTURY; // XOR; CC_FULL_YEAR and CC_HALF_CENTURY
                                           // at the same time may not be used
 
 first_year=1;
 last_year=9999;
 
 if((this->flags & CC_SHORT_YEAR)==CC_SHORT_YEAR)
 {
  textlen=8;
  
  // Changing first and last acceptable years
  // Working in range of century defined by year variable
  
  if(year<first_year || year>last_year)
  {
   // Range is set relative to today's year
   struct tm *dat;
   time_t tmp;
   time(&tmp);
   dat=localtime(&tmp);
   
   year=dat->tm_year+1900; // today's year
  }
  
  first_year=(year/100)*100+1; // dividing with loosing rest
  last_year=first_year+99;
  if(last_year==10000) last_year--; // full century
  
  if((this->flags & CC_HALF_CENTURY)==CC_HALF_CENTURY)
  {
   if(year<51 || year>9950)
    this->flags=this->flags^CC_HALF_CENTURY; // HALF_CENTURY may nor be set
   else
   {
    if((year%100)>50)
    {
     first_year+=50;
     last_year+=50;
    }
    else
    {
     first_year-=50;
     last_year-=50;
    }
   }
  }
 }
 else textlen=10; // length of text string
 
 SetDate(day,month,year);
 
 float width=0;
 BString s("");
 for(char i='0'; i<='9'; i++)
 {
  s<<i;
  if(StringWidth(s.String())>width) width=StringWidth(s.String());
  s.SetTo("");
 }
 
 ResizeTo(width*(textlen-2)+StringWidth("/")*2.5, LineHeight()-1);
 
 SetWordWrap(false);
 SetMaxBytes(textlen);
 SetTextRect(Bounds());
 SetDoesUndo(false);
 for(int32 i=0;i<256;i++) DisallowChar(i);
 for(int32 i='0';i<='9';i++) AllowChar(i);
}


///////////////////////////////////////////////////////
bool DateTextView::AcceptsDrop(const BMessage *message)
{
 return false;
}


/////////////////////////////////////////////////
bool DateTextView::AcceptsPaste(BClipboard *clip)
{
 return false;
}


////////////////////////////////////////
void DateTextView::Cut(BClipboard *clip)
{
 Copy(clip);
}


////////////////////////////////////////////////////////
void DateTextView::DeleteText(int32 start, int32 finish)
{
 BTextView::DeleteText(start,finish);
 if(is_ins)
 {
  if(start==2 || start==5) InsertText(div[divider],1,start,NULL);
  else InsertText("0",1,start,NULL);
 }
}


/////////////////////////////////////////////////////////////
void DateTextView::KeyDown(const char *bytes, int32 numBytes)
{
 if(!enabled) if(bytes[0]!=B_TAB) return;
 
 int32 i1,i2;
 GetSelection(&i1,&i2);
 
 if(bytes[0]>='0' && bytes[0]<='9')
 {
  if(i1>(textlen-1)) return; // not to insert after end of string
  Select(i1,i1);
  if(i1==2 || i1==5)
  {
   i1++;
   Select(i1,i1);
  }
  DeleteText(i1,i1+1);
  BTextView::KeyDown(bytes, numBytes);
 }
 else if(bytes[0]==B_DELETE)
 {
  if(i1>(textlen-1)) return; // not to insert after end of string
  Select(i1,i1);
  is_ins=true; // symbol "0" or divider will be inserted
  BTextView::KeyDown(bytes, numBytes);
  is_ins=false;
 }
 else if(bytes[0]==B_BACKSPACE)
 {
  Select(i1,i1);
  is_ins=true;
  BTextView::KeyDown(bytes, numBytes);
  is_ins=false;
 }
 else if(bytes[0]==B_TAB)
 {
  Parent()->KeyDown(bytes, numBytes);
 }
 else if(bytes[0]==B_DOWN_ARROW)
 {
  // Is Ctrl+DownArrow pressed?
  if(modifiers() & B_CONTROL_KEY)
  {
   // yes
   BMessage msg(myButtonMessage);
   Parent()->MessageReceived(&msg);
  }
  else
   BTextView::KeyDown(bytes, numBytes);
 }
 else 
  BTextView::KeyDown(bytes, numBytes);
}


///////////////////////////////////////////////
void DateTextView::MakeFocus(bool focused=true)
{
 BTextView::MakeFocus(focused);
 
 int day, month, year;
 GetDate(&day, &month, &year);
 Parent()->Draw(Parent()->Bounds());
}


//////////////////////////////////////////
void DateTextView::Paste(BClipboard *clip)
{
 return;
}


///////////////////////////////////////////
void DateTextView::SetEnabled(bool enabled)
{
 this->enabled=enabled;
 SetFlags(Flags()^B_NAVIGABLE);
 MakeEditable(enabled);
 
 BFont font;
 rgb_color color;
 GetFontAndColor((int32) 0, &font, &color);
 color.alpha=0;
 if(enabled)
 {
   SetViewColor(255,255,255,255);
  color.red=color.green=color.blue=0;
 }
 else
 {
   SetViewColor(239,239,239,255);
   color.red=color.green=color.blue=128;
 }
 
 SetFontAndColor(&font,B_FONT_ALL,&color);
 Invalidate();
}


/////////////////////////////////////////////////////////
void DateTextView::DrawDate(int day, int month, int year)
{
 // It is assumed that date is correct
 BString s;
 s.SetTo("");
 
 if(!((flags & CC_MM_DD_YYYY_FORMAT)==CC_MM_DD_YYYY_FORMAT))
 {
  if(day<10) s.Append("0");
  s<<day;
  
  s.Append(div[divider]);
  
  if(month<10) s.Append("0");
  s<<month;
 }
 else // CC_MM_DD_YYYY_FORMAT
 {
  if(month<10) s.Append("0");
  s<<month;
  
  s.Append(div[divider]);
  
  if(day<10) s.Append("0");
  s<<day;
 }
 s.Append(div[divider]);
 
 if((flags & CC_SHORT_YEAR)==CC_SHORT_YEAR)
 {
  int year1=year%100;
  if(year1<10) s.Append("0");
  s<<year1;
 }
 else // FULL_YEAR
 {
  if(year<10) s.Append("000");
  else if(year<100) s.Append("00");
  else if(year<1000) s.Append("0");
  s<<year;
 }
 
 SetText(s.String());
}


///////////////////////////////////////////////////////////
void DateTextView::GetDate(int *day, int *month, int *year)
{
 int mday=*day;
 int mmonth=*month;
 int myear=*year;
 
 BString s(Text());
 char n1[11];
 char n2[11];
 char n3[11];
 
 s.CopyInto(n1,0,2);
 n1[2]='\0';
 s.CopyInto(n2,3,2);
 n2[2]='\0';
 
 if((flags & CC_SHORT_YEAR)==CC_SHORT_YEAR)
 {
  s.CopyInto(n3,6,2);
  n3[2]='\0';
 }
 else // FULL_YEAR
 {
  s.CopyInto(n3,6,4);
  n3[4]='\0';
 }
 
 if(!((flags & CC_MM_DD_YYYY_FORMAT)==CC_MM_DD_YYYY_FORMAT))
 {
  mday=atoi(n1);
  mmonth=atoi(n2);
 }
 else
 {
  mday=atoi(n2);
  mmonth=atoi(n1);
 }
 
 myear=atoi(n3);
 if((flags & CC_SHORT_YEAR)==CC_SHORT_YEAR)
 {
  if((flags & CC_HALF_CENTURY)==CC_HALF_CENTURY) 
  {
   if(myear<51) myear+=50; else myear-=50;
  }
  else if(myear==0) myear=100;
  
  myear+=(first_year-1);
 }
 
 if(!VerifyDate(&mday,&mmonth,&myear)) SetDate(mday,mmonth,myear);
 
 *day=mday;
 *month=mmonth;
 *year=myear;
 
 return;
}


////////////////////////////////////////////////////////
void DateTextView::SetDate(int day, int month, int year)
{
 int mday=day;
 int mmonth=month;
 int myear=year;
 VerifyDate(&mday, &mmonth, &myear);
 DrawDate(mday, mmonth, myear);
}


/////////////////////////////////////////////
void DateTextView::SetDate(const char *tdate)
{
 // Almost the same as GetDate. May be to combine them.
 
 // Changes text using current settings of control.
 int day;
 int month;
 int year;
 
 int k;
 
 bool short_year=false;
 if((flags & CC_SHORT_YEAR)==CC_SHORT_YEAR) short_year=true;
 
 char n1[3]="00";
 char n2[3]="00";
 char n3[5]="0000";
 if(short_year) n3[2]='\0';
 
 bool zero=false; // was the end of tdate string?
 
 int c=0;
 while (!(c==2 || zero))
 {
  if(tdate[c]=='\0') zero=true;
  else n1[c]=tdate[c];
  c++;
 }

 if(zero) goto L1;
 if(tdate[2]=='\0') goto L1;
 
 c=0;
 while (!(c==2 || zero))
 {
  if(tdate[c+3]=='\0') zero=true;
  else n2[c]=tdate[c+3];
  c++;
 }

 if(zero) goto L1;
 if(tdate[5]=='\0') goto L1;
 
 k=short_year ? 2 : 4;
 
 c=0;
 while (!(c==k || zero))
 {
  if(tdate[c+6]=='\0') zero=true;
  else n3[c]=tdate[c+6];
  c++;
 }
 
L1:
 if(!((flags & CC_MM_DD_YYYY_FORMAT)==CC_MM_DD_YYYY_FORMAT))
 {
  day=atoi(n1);
  month=atoi(n2);
 }
 else
 {
  day=atoi(n2);
  month=atoi(n1);
 }
 
 year=atoi(n3);
 if(short_year)
 {
  if((flags & CC_HALF_CENTURY)==CC_HALF_CENTURY) 
  {
   if(year<51) year+=50; else year-=50;
  }
  else if(year==0) year=100;
  
  year+=(first_year-1);
 }
 SetDate(day,month,year);
}


//////////////////////////////////////////////////////////////
bool DateTextView::VerifyDate(int *day, int *month, int *year)
{
 // Function verifies date to be correct and changes it if it's needed
 // Returns true if date was correct (and wasn't changed)
 
 struct tm *dat;
 time_t tmp;
 time(&tmp);
 dat=localtime(&tmp);
 
 bool flag=true; // date is correct
 
 if((flags & CC_SHORT_YEAR)==CC_SHORT_YEAR)
 {
  if(*year<first_year || *year>last_year)
  {
   int year1=*year%100;
   if((flags & CC_HALF_CENTURY)==CC_HALF_CENTURY) 
   {
    if(year1<51) year1+=50; else year1-=50;
   }
   else if(year1==0) year1=100;
   
   *year=year1+first_year-1;
   flag=false;
  }
 }
 else // FULL_YEAR
 {
  if(*year<1 || *year>9999)
  {
   *year=dat->tm_year+1900;
   flag=false;
  }
 }
 
 if(*month<1 || *month>12)
 {
  *month=dat->tm_mon+1;
  flag=false;
 }
 
 if(*day<1 || *day>31)
 {
  *day=dat->tm_mday;
  flag=false;
 }
 
 if((*month==4 || *month==6 || *month==9 || *month==11) && *day>30)
 {
  if((*day=dat->tm_mday)>30) *day=30;
  flag=false;
 }
 else if (*month==2)
 {
  int tmpday;
  
  if((*year)%4==0) // leap year?
  {
   if((*year)%100==0 && (*year)%400!=0) tmpday=28; // no
   else tmpday=29; // yes
  }
  else tmpday=28;
  
  if(*day>tmpday)
  {
   if((*day=dat->tm_mday)>tmpday) *day=tmpday;
   flag=false;
  }
 }
 
 return flag;
}


////////////////////////////////////////////////////////////////
void DateTextView::GetYearRange(int *first_year, int *last_year)
{
 *first_year=this->first_year;
 *last_year=this->last_year;
}


/////////////////////////////////
uint32 DateTextView::GetDivider()
{
 return divider;
}


///////////////////////////////////////////
void DateTextView::SetDivider(uint32 dvder)
{
 if(dvder<0 || dvder>LAST_DIVIDER) dvder=0;
 
 BString s(Text());
 SetText((s.ReplaceAll(div[divider],div[dvder])).String());
 this->divider=dvder;
}


///////////////////////////////////
uint32 DateTextView::GetDateFlags()
{
 return flags;
}


///////////////////////////////////////////
void DateTextView::SetDateFlags(uint32 fmt)
{
 int mday, mmonth, myear;
 GetDate(&mday, &mmonth, &myear);
 
 // Blocking changing of parameters of year
 // (full/short year, full/half century)
 fmt=fmt & 0xFFE7;
 
 if(fmt<0 || fmt>LAST_FORMAT) fmt=0;
 
 flags=fmt | (flags & 0xFFFE); // LAST_FORMAT==1, 1 bit
 
 SetDate(mday, mmonth, myear);
}