/*******************************************************
*   SplitPane©
*
*   SplitPane is a usefull UI component. It alows the 
*   use to ajust two view Horizontaly or Vertacly so
*   that they are a desired size. This type of Pane 
*   shows up most comonly in Mail/News Readers.
*
*   @author  YNOP (ynop@acm.org)
*   @version beta
*   @date    Dec. 10 1999
*******************************************************/
#include <AppKit.h>
#include <InterfaceKit.h>
#include <StorageKit.h>
#include <String.h>
#include <Path.h>
#include <TranslationKit.h>
#include <TranslationUtils.h>

//#include <stdio.h>

#include "SplitPane.h"
//#include "SplitPaneConfig.h"

/*******************************************************
*   Setup the main view. Add in all the niffty components
*   we have made and get things rolling
*******************************************************/
SplitPane::SplitPane(BRect frame, const char* name, BView *one, BView *two,uint32 Mode):BView(frame, name, Mode,B_WILL_DRAW|B_FRAME_EVENTS){
   SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));  // This is default get from parent if exist
   //SetViewColor(B_TRANSPARENT_32_BIT); // go tran so we have control over drawing
   BRect b;
   b = Bounds();
   SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
   PaneOne = one;
   PaneTwo = two;

   align = B_VERTICAL; // Most people use it this way
   pos = (int)b.Width()/2; // Center is a good start place
   thickness = 10;
   jump = 1; // 1 makes a smother slide
   VOneDetachable = false;
   VTwoDetachable = false;
   pad = 1;
   MinSizeOne = 0; // full left
   MinSizeTwo = 0; // full right
   poslocked = false; // free movement
   alignlocked = false; // free alignment
   Draggin = false;
   attached = false;

   WinOne = NULL;
   WinTwo = NULL;
   ConfigWindow = NULL;

   AddChild(one);
   AddChild(two);
}

/*******************************************************
*   When ready grap the parents color and refreash.
*******************************************************/
void SplitPane::AttachedToWindow(){
   //SetViewColor(Parent()->ViewColor());
   attached = true;
   Update();
}

/*******************************************************
*   If we are being resized. Fix the stuff we need to fix
*******************************************************/
void SplitPane::FrameResized(float,float){
//   if bar is on the left side follow left
//   else if it is on the right side follow the right
//   Need to implements smart follow still
   Update();
   Invalidate();
}

/*******************************************************
*   The main draw stuff here. basicly just the slider
*******************************************************/
void SplitPane::Draw(BRect f){
   SetHighColor(160,160,160);

   if(align == B_VERTICAL){
      SetHighColor(185,185,185);
      // SetHighColor(145,145,145);
      //FillRect(BRect(pos,Bounds().top+pad+1,pos,Bounds().bottom-pad-1)); // 145
      FillRect(BRect(pos,Bounds().top+pad+1,pos,Bounds().bottom-pad-1)); // 145
   
      SetHighColor(255,255,255);
      FillRect(BRect(pos+1,Bounds().top+pad+1,pos+2,Bounds().bottom-pad-1)); // 255
   
      //SetHighColor(216,216,216);
      SetHighColor(Parent()->ViewColor());
      FillRect(BRect(pos+2,Bounds().top+pad+1,pos+thickness-2,Bounds().bottom-pad-1));// 216

      if(thickness>9)
      {
	float y = (Bounds().bottom - Bounds().top)/2;
	float x = pos + (thickness/2);
	SetPenSize(2);
      	SetHighColor(255,255,255);
	StrokeLine(BPoint(x-3,y-11),BPoint(x+3,y-5));
	StrokeLine(BPoint(x-3,y-7),BPoint(x+3,y-1));
	StrokeLine(BPoint(x-3,y-3),BPoint(x+3,y+3));
	StrokeLine(BPoint(x-3,y+1),BPoint(x+3,y+7));
	StrokeLine(BPoint(x-3,y+5),BPoint(x+3,y+11));
	SetPenSize(1);
      	SetHighColor(145,145,145);
	StrokeLine(BPoint(x-3,y-10),BPoint(x+3,y-4));
	StrokeLine(BPoint(x-3,y-6),BPoint(x+3,y+0));
	StrokeLine(BPoint(x-3,y-2),BPoint(x+3,y+4));
	StrokeLine(BPoint(x-3,y+2),BPoint(x+3,y+8));
	StrokeLine(BPoint(x-3,y+6),BPoint(x+3,y+12));
      }
   
      SetHighColor(185,185,185);
      // SetHighColor(145,145,145);
      FillRect(BRect(pos+thickness-2,Bounds().top+pad+1,pos+thickness-2,Bounds().bottom-pad-1)) ;// 145
   
      SetHighColor(145,145,145);
      FillRect(BRect(pos+thickness-1,Bounds().top+pad+1,pos+thickness-1,Bounds().bottom-pad-1)); // 96
      //FillRect(BRect(pos+thickness,Bounds().top+pad+1,pos+thickness,Bounds().bottom-pad-1));
   }else{
      SetHighColor(185,185,185);
      // SetHighColor(145,145,145);
      //FillRect(BRect(Bounds().left+pad+1,pos,Bounds().right-pad-1,pos)); // 145
      FillRect(BRect(Bounds().left+pad+1,pos,Bounds().right-pad-1,pos)); // 145
   
      SetHighColor(255,255,255);
      FillRect(BRect(Bounds().left+pad+1,pos+1,Bounds().right-pad-1,pos+2)); // 255
   
      //SetHighColor(216,216,216);
      SetHighColor(Parent()->ViewColor());
      FillRect(BRect(Bounds().left+pad+1,pos+2,Bounds().right-pad-1,pos+thickness-2));// 216

      if(thickness>9)
      {
      	SetHighColor(255,255,255);
	float x = (Bounds().right - Bounds().left)/2;
	float y = pos + (thickness/2);
	SetPenSize(2);
	StrokeLine(BPoint(x-11,y-3),BPoint(x-5,y+3));
	StrokeLine(BPoint(x-7,y-3),BPoint(x-1,y+3));
	StrokeLine(BPoint(x-3,y-3),BPoint(x+3,y+3));
	StrokeLine(BPoint(x+1,y-3),BPoint(x+7,y+3));
	StrokeLine(BPoint(x+5,y-3),BPoint(x+11,y+3));
	SetPenSize(1);
      	SetHighColor(145,145,145);
	StrokeLine(BPoint(x-10,y-3),BPoint(x-4,y+3));
	StrokeLine(BPoint(x-6,y-3),BPoint(x+0,y+3));
	StrokeLine(BPoint(x-2,y-3),BPoint(x+4,y+3));
	StrokeLine(BPoint(x+2,y-3),BPoint(x+8,y+3));
	StrokeLine(BPoint(x+6,y-3),BPoint(x+12,y+3));
      }
   
      SetHighColor(185,185,185);
      // SetHighColor(145,145,145);
      FillRect(BRect(Bounds().left+pad+1,pos+thickness-2,Bounds().right-pad-1,pos+thickness-2)) ;// 145
   
      SetHighColor(145,145,145);
      // SetHighColor(96,96,96);
      FillRect(BRect(Bounds().left+pad+1,pos+thickness-1,Bounds().right-pad-1,pos+thickness-1)); // 96
      //FillRect(BRect(Bounds().left+pad+1,pos+thickness,Bounds().right-pad-1,pos+thickness));
   }
}

/*******************************************************
*   Keeps Modes for both panles uptodate and acctually
*   is the func that sets the location of the slider
*******************************************************/
void SplitPane::Update(){
   Window()->Lock();
   if(align == B_VERTICAL){
      PaneOne->SetResizingMode(B_FOLLOW_LEFT|B_FOLLOW_TOP_BOTTOM);
      PaneTwo->SetResizingMode(B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP_BOTTOM);
      if(pos > (Bounds().Width()-thickness-MinSizeTwo)){
         if(!poslocked){
            pos = (int)Bounds().Width()-thickness-MinSizeTwo;
         }
      }
      if(pos < MinSizeOne){
         if(!poslocked){
            pos = MinSizeOne;
         }
      }
   }else{
      PaneOne->SetResizingMode(B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP);
      PaneTwo->SetResizingMode(B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP_BOTTOM);
      if(pos > (Bounds().Height()-thickness-MinSizeTwo)){
         if(!poslocked){
            pos = (int)Bounds().Height()-thickness-MinSizeTwo;
         }
      }
      if(pos < MinSizeOne){
         if(!poslocked){
            pos = MinSizeOne;
         }
      }
   }
  
   // this block should go in FrameResized .. think about it
   if(align == B_VERTICAL){
      if(pos >= (Bounds().IntegerWidth()/2)){
         //pos should follow the right side
         // staying the same distans from it that
         // it is right now
      }
   }else{
      if(pos >= (Bounds().IntegerHeight()/2)){
         //should follow bottom and stay the
         // same distance that we are way from
         // it now
      }
   }
   
   
   if(PaneOne){
      if(!WinOne){
         if(align == B_VERTICAL){
            PaneOne->MoveTo(pad,Bounds().top+pad);
            PaneOne->ResizeTo(pos-pad, Bounds().Height()-pad-pad); // widht x height
         }else{
            PaneOne->MoveTo(pad,Bounds().top+pad);
            PaneOne->ResizeTo(Bounds().Width()-pad-pad, pos-pad-pad); // widht x height
         }
      }
   }
   if(PaneTwo){
      if(!WinTwo){   
         if(align == B_VERTICAL){  
            PaneTwo->MoveTo(pos+thickness,Bounds().top+pad);
            PaneTwo->ResizeTo(Bounds().Width()-(pos+thickness)-pad, Bounds().Height()-pad-pad);
         }else{
            PaneTwo->MoveTo(Bounds().left+pad,pos+thickness);
            PaneTwo->ResizeTo(Bounds().Width()-pad-pad, Bounds().Height()-pos-pad-thickness);
         }
      }
   }

   Window()->Unlock();
}

/*******************************************************
*   Hook for when we click. This takes care of all the 
*   little stuff - Like where is the mouse and what is
*   going on.
*******************************************************/
void SplitPane::MouseDown(BPoint where){
   Window()->Lock();
   BMessage *currentMsg = Window()->CurrentMessage();
   if (currentMsg->what == B_MOUSE_DOWN) {
      uint32 buttons = 0;
      currentMsg->FindInt32("buttons", (int32 *)&buttons);
      uint32 modifiers = 0;
      currentMsg->FindInt32("modifiers", (int32 *)&modifiers);
      uint32 clicks = 0;
      currentMsg->FindInt32("clicks",(int32*)&clicks);
      
      if (buttons & B_SECONDARY_MOUSE_BUTTON){
         if(!alignlocked){
            switch(align){
            case B_VERTICAL:
               align = B_HORIZONTAL;
               break;
            case B_HORIZONTAL:
               align = B_VERTICAL;
               break;
            }
            Update();
            Invalidate();
         }
         
         /*if(VOneDetachable){
            WinOne = new BWindow(ConvertToScreen(PaneOne->Bounds()),"PanelOne",B_TITLED_WINDOW,B_ASYNCHRONOUS_CONTROLS);
            RemoveChild(PaneOne);
            WinOne->AddChild(PaneOne);
            PaneOne->SetResizingMode(B_FOLLOW_ALL_SIDES);
            // PaneOne->SetTarget(this);
            WinOne->Show();
         }*/
      }
     // if((buttons & B_PRIMARY_MOUSE_BUTTON) && (clicks >= 2)){
         //Config window for split pane
     //    (new BAlert(NULL,"This is - or will be - a configuration panel for SplitPane.","Ok"))->Go();
         //ConfigWindow = new SplitPaneConfig(this);
         //ConfigWindow->Show();
      //}else 
      if((buttons & B_PRIMARY_MOUSE_BUTTON) && !Draggin){
         if(!poslocked){
            Draggin= true; // this is so we can drag
		    here = where;
		 }
		 SetMouseEventMask(B_POINTER_EVENTS,B_LOCK_WINDOW_FOCUS);
	  }
   }
   Window()->Unlock();
}

/*******************************************************
*   If we unclick then stop dragging or whatever it is 
*   we are doing
*******************************************************/
void SplitPane::MouseUp(BPoint where){
   Draggin = false; // stop following mouse
}

/*******************************************************
*   If the mouse moves while we dragg. Then follow it
*   Also Invalidate so we update the views
*******************************************************/
void SplitPane::MouseMoved(BPoint where,uint32 info,const BMessage *m){
   if(Draggin){
      switch(align){
      case B_HORIZONTAL:
         pos = (int)(where.y)-(thickness/2);//- here.x
         break;
      case B_VERTICAL:
         pos = (int)(where.x)-(thickness/2);
         break;
      }

      /*
      // This code figures out which jump we are closest
      // to and if needed we "snap" to that.
      int c = Bounds().IntegerWidth() / pos
      Jump * c ... hmmm this is not right at all
      */
      
      if(pos < MinSizeOne){
         pos = MinSizeOne;
      }

      if(align == B_VERTICAL){
         if(pos > (Bounds().Width() - thickness - MinSizeTwo)){
            pos = (int)(Bounds().Width() - thickness - MinSizeTwo + 1);
         }
      }else{
         if(pos > (Bounds().Height() - thickness - MinSizeTwo)){
            pos = (int)(Bounds().Height() - thickness - MinSizeTwo + 1);
         }
      }
      
      Update();

      Invalidate();
   }
}

/*******************************************************
*   If you already have a view One, but want to change
*   if for some odd reason. This should work.
*******************************************************/
void SplitPane::AddChildOne(BView *v){
   RemoveChild(PaneOne);
   PaneOne = v;
   AddChild(PaneOne);
}
/*void SplitPane::MakePaneTwoFocus()
{
	if(PaneTwo)
		PaneTwo->MakeFocus();

}
*/
/*******************************************************
*   If you already have a view Two, and want to put 
*   another view there, this is what to use.
*******************************************************/
void SplitPane::AddChildTwo(BView* v,bool IsAdded,bool ShowAfterHide)
{ 
	if(!v->IsHidden())
		v->Hide();
	
	PaneTwo = v;
	//WinTwo = NULL;

		
	PaneTwo = v;
	if(IsAdded)
	{
		Update();
		if(ShowAfterHide)
		{
			if(v->IsHidden())
				v->Show();
		}

	}
	if(!IsAdded)
	{
		AddChild(PaneTwo);
	}
	PaneTwo = v;

}

/*******************************************************
*   Sets is we are horizontal or Vertical. We use the 
*   standard B_HORIZONTAL and B_VERTICAL flags for this
*******************************************************/
void SplitPane::SetAlignment(uint a){
   align = a;
   if(attached){
      Update();
   }
   Invalidate();
}

/*******************************************************
*   Returns wheather the slider is horizontal or vertical
*******************************************************/
uint SplitPane::GetAlignment(){
   return align;
}

/*******************************************************
*   Sets the location of the bar. (we do no bounds 
*   checking for you so if its off the window thats 
*   your problem)
*******************************************************/
void SplitPane::SetBarPosition(int i){
   pos = i;
   if(attached){
      Update();
   }
   Invalidate();
}

/*******************************************************
*   Returns about where the bar is ...
*******************************************************/
int SplitPane::GetBarPosition(){
   return pos;   
}

/*******************************************************
*   Sets how thick the bar should be.
*******************************************************/
void SplitPane::SetBarThickness(int i){
   thickness = i;
   if(attached){
      Update();
   }
   Invalidate();
}

/*******************************************************
*   Retuns to us the thickness of the slider bar
*******************************************************/
int SplitPane::GetBarThickness(){
   return thickness;
}

/*******************************************************
*   Sets the amount of jump the bar has when it is 
*   moved. This can also be though of as snap. The bar
*   will start at 0 and jump(snap) to everry J pixels.
*******************************************************/
void SplitPane::SetJump(int i){
   jump = i;
   if(attached){
      Update();
   }
}

/*******************************************************
*   Lets you know what the jump is .. see SetJump
*******************************************************/
int SplitPane::GetJump(){
   return jump;   
}

/*******************************************************
*   Do we have a View One or is it NULL
*******************************************************/
bool SplitPane::HasViewOne(){
   if(PaneOne) return true;
   return false;   
}

/*******************************************************
*   Do we have a View Two .. or is it NULL too
*******************************************************/
bool SplitPane::HasViewTwo(){
   if(PaneTwo) return true;
   return false;
}

/*******************************************************
*   Sets wheather View one is detachable from the 
*   slider view and from the app. This will creat a 
*   window that is detached (floating) from the app.
*******************************************************/
void SplitPane::SetViewOneDetachable(bool b){
   VOneDetachable = b;    
}

/*******************************************************
*   Sets view tow detachable or not
*******************************************************/
void SplitPane::SetViewTwoDetachable(bool b){
   VTwoDetachable = b;
}

/*******************************************************
*   Returns whether the view is detachable
*******************************************************/
bool SplitPane::IsViewOneDetachable(){
   return VOneDetachable;
}

/*******************************************************
*   Returns if this view is detachable
*******************************************************/
bool SplitPane::IsViewTwoDetachable(){
   return VTwoDetachable;
}

/*******************************************************
*   Tells the view if the user is alowed to open the 
*   configuration window for the slider.
*******************************************************/
void SplitPane::SetEditable(bool b){
   //ADD CODE HERE YNOP
}

/*******************************************************
*   Tells use if the split pane is user editable
*******************************************************/
bool SplitPane::IsEditable(){
   return true; //ADD SOME MORE CODE HERE
}

/*******************************************************
*   Sets the inset that the view has.
*******************************************************/
void SplitPane::SetViewInsetBy(int i){
   pad = i;
   if(attached){
      Update();
   }   Invalidate();
}

/*******************************************************
*   Returns to use the padding around the views
*******************************************************/
int SplitPane::GetViewInsetBy(){
   return pad;
}

/*******************************************************
*   This sets the minimum size that View one can be.
*   if the user trys to go past this .. we just stop
*   By default the minimum size is set to 0 (zero) so
*   the user can put the slider anywhere.
*******************************************************/
void SplitPane::SetMinSizeOne(int i){
   MinSizeOne = i;
}

/*******************************************************
*   Gives us the minimum size that one can be.
*******************************************************/
int SplitPane::GetMinSizeOne(){
   return MinSizeOne;
}

/*******************************************************
*   This sets the Minimum size that the second view 
*   can be.
*******************************************************/
void SplitPane::SetMinSizeTwo(int i){
   MinSizeTwo = i;
}

/*******************************************************
*   Lets us know what that minimum size is.
*******************************************************/
int SplitPane::GetMinSizeTwo(){
   return MinSizeTwo;
}

/*******************************************************
*   Locks the bar from being moved by the User. The
*   system can still move the bar (via SetBarPosition)
*******************************************************/
void SplitPane::SetBarLocked(bool b){
   poslocked = b;
}

/*******************************************************
*   Returns to use if the bar is in a locked state or 
*   not.
*******************************************************/
bool SplitPane::IsBarLocked(){
   return poslocked;
}

/*******************************************************
*   Locks the alignment of the bar. The user can no 
*   longer toggle between Horizontal and Vertical
*   Slider bar. Again you can still progomaticly set 
*   the position how ever you want.
*******************************************************/
void SplitPane::SetBarAlignmentLocked(bool b){
   alignlocked = b;
}

/*******************************************************
*   Lets us know about the lock state of the bar
*******************************************************/
bool SplitPane::IsBarAlignmentLocked(){
   return alignlocked;
}

/*******************************************************
*   Gets the Total state of the bar, alignment, size,
*   position and many other things that are required
*   to fully capture the state of the SplitPane.
*   We pack all of this into a cute little BMessage
*   so that it is esally expandable and can be saved 
*   off easyaly too.  The SplitPane System does not 
*   however save the state for you. Your program must
*   grab the state and save it in its config file.
*******************************************************/
BMessage* SplitPane::GetState(){
   BMessage *state;
   
   state = new BMessage(SPLITPANE_STATE);
   
   state->AddBool("onedetachable",VOneDetachable);
   state->AddBool("twodetachable",VTwoDetachable);
   state->AddInt32("align",align);
   state->AddInt32("pos",pos);
   state->AddInt32("thick",thickness);   
   state->AddInt32("jump",jump);
   state->AddInt32("pad",pad);
   state->AddInt32("minsizeone",MinSizeOne);
   state->AddInt32("minsizetwo",MinSizeTwo);
   state->AddBool("poslock",poslocked);
   state->AddBool("alignlock",alignlocked);
   return state;
  // delete state;
}

/*******************************************************
*   Sets the state of the SplitPane from a BMessage 
*   like the one recived from GetState(). 
*   This is one of three ways the user can rebuild the
*   state of the SplitPane. The second is to simply 
*   send the SplitPane the state message, it is the 
*   same as calling SetState but it ashyncronouse.
*   The third way is to use all the Get/Set methouds
*   for each element of the SplitPane, this way is 
*   long and boarding. I suggest you just send the 
*   View a message :)
*******************************************************/
void SplitPane::SetState(BMessage *state){
   int32 Npos,Nthickness,Njump,Npad,NMSO,NMST;
   int32 Nalign;//uint 

   if(state->FindBool("onedetachable",&VOneDetachable) != B_OK){
      VOneDetachable = false;
   }
   if(state->FindBool("towdetachable",&VTwoDetachable) != B_OK){
      VTwoDetachable = false;
   }
   if(state->FindInt32("align",&Nalign) == B_OK){
      align = Nalign;
   }   
   if(state->FindInt32("pos",&Npos) == B_OK){
      pos = Npos;
   }
   if(state->FindInt32("thick",&Nthickness) == B_OK){
      thickness = Nthickness;  
   }
   if(state->FindInt32("jump",&Njump) == B_OK){
      jump = Njump;
   }
   if(state->FindInt32("pad",&Npad) == B_OK){
      pad = Npad;
   }
   if(state->FindInt32("minsizeonw",&NMSO) == B_OK){
      MinSizeOne = NMSO;
   }
   if(state->FindInt32("minsizetwo",&NMST) == B_OK){
      MinSizeTwo = NMST;
   }
   if(state->FindBool("poslock",&poslocked) != B_OK){
      poslocked = false;
   }
   if(state->FindBool("alignlock",&alignlocked) != B_OK){
      alignlocked = false;
   }

   Update();
   Invalidate();
}

/*******************************************************
*   Ok, hmm what does this do. NOT MUCH. if we get a 
*   STATE message then lets set the state. This is here
*   to provide a asyncronuse way of seting the state and
*   also to make life easyer.
*******************************************************/
void SplitPane::MessageReceived(BMessage *msg){
   switch(msg->what){
   case SPLITPANE_STATE:
      SetState(msg);   
      break;
   default:
      BView::MessageReceived(msg);
      break;
   }
}