/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

//
// CScrollerWithArrows
// Mike Pinkerton, Netscape Communications
//
// See header file for class descriptions.
//
// NOTES:
// - does not yet autoscroll when doing drag and drop
// - icons for horizontal orientation are incorrect, but we don't have any better ones
//		in the app so I just put the vertical ones in as place holders.
// - has only been tested in vertical orientation
// - has only been tested with all indents set to 0
//

#include "CScrollerWithArrows.h"

#include <LStdControl.h>
#include <PP_Types.h>
#include <UGAColorRamp.h>

#pragma mark -- class CScrollArrowControl

//
// CScrollerWithArrows
//
// Default contsructor
//
CScrollerWithArrows::CScrollerWithArrows()
{
	mScrollingView = nil;
	mScrollingViewID = -1;
	mUpLeftArrow = nil;
	mDownRightArrow = nil;
	mIsVertical = false;
}


//
// CScrollerWithArrows								
//
// Stream Constructor
//
CScrollerWithArrows::CScrollerWithArrows(
	LStream	*inStream)
		: LView(inStream)
{
	SScrollerArrowInfo scrollerInfo;
	inStream->ReadData(&scrollerInfo, sizeof(SScrollerArrowInfo));
	
	// ScrollingView has not yet been created, since SuperViews are
	// created before their subviews when building Panes from a Stream.
	// Therefore, we store the ID of the ScrollingView so that the
	// FinishCreateSelf function can set up the proper connections.		
	mScrollingViewID = scrollerInfo.scrollingViewID;
	mScrollingView = nil;
	
	mUpLeftArrow = nil;
	mDownRightArrow = nil;

	// Determine orientation of scrolling. I guess I could just make this 
	// a flag in constructor....
	if ( mFrameSize.height > mFrameSize.width )
		mIsVertical = true;
		
	try {
		MakeScrollArrows(scrollerInfo.leftIndent, scrollerInfo.rightIndent,
							scrollerInfo.topIndent, scrollerInfo.bottomIndent);
	}	
	catch (...) {					// Failed to fully build scroll arrows
		delete mUpLeftArrow;
		delete mDownRightArrow;
		throw;
	}
	
} // CScrollerWithArrows stream constructor


//
// ~CScrollerWithArrows
//
CScrollerWithArrows::~CScrollerWithArrows()
{
	// view subsystem will dispose of scroll arrows because they are children
}


//
// MakeScrollArrows
//
// Create the arrow control objects in the view, indented accordingly to fit the
// needs of the subview. In contrast to how LScroller works, the indents apply to
// both scrollers.
//
void
CScrollerWithArrows::MakeScrollArrows(
	Int16			inLeftIndent,
	Int16			inRightIndent,
	Int16			inTopIndent,
	Int16			inBottomIndent)
{
	SPaneInfo arrowInfo;				// Common information for scroll arrows
	arrowInfo.visible = false;			// arrows aren't visible until
	arrowInfo.enabled = true;			//    scroller is activated
	arrowInfo.userCon = 0;
	arrowInfo.superView = this;	
	arrowInfo.paneID = 0;	
	
	if ( IsVertical() ) {
	
		arrowInfo.bindings.left = true;
		arrowInfo.bindings.right = true;
		
		arrowInfo.width = mFrameSize.width - inLeftIndent - inRightIndent;
		arrowInfo.height = 16;
		arrowInfo.left = inLeftIndent;
		
		// create the top vertical arrow
		arrowInfo.top = inTopIndent;
		arrowInfo.bindings.bottom = false;
		mUpLeftArrow = MakeOneScrollArrow ( arrowInfo, kUpLeft );								
		mUpLeftArrow->AddListener(this);
		
		// create the bottom vertical arrow
		arrowInfo.top = mFrameSize.height - inBottomIndent - arrowInfo.height;
		arrowInfo.bindings.top = false;
		arrowInfo.bindings.bottom = true;
		mDownRightArrow = MakeOneScrollArrow ( arrowInfo, kDownRight );								
		mDownRightArrow->AddListener(this);
			
	} // if view orientation is vertical
	else {
	
		arrowInfo.bindings.top = true;
		arrowInfo.bindings.bottom = true;
		
		arrowInfo.width = 16;
		arrowInfo.height = mFrameSize.height - inTopIndent - inBottomIndent;
		arrowInfo.top = inTopIndent;
		
		// create the left horizontal arrow
		arrowInfo.left = inLeftIndent;
		arrowInfo.bindings.right = false;
		mUpLeftArrow = MakeOneScrollArrow ( arrowInfo, kUpLeft );								
		mUpLeftArrow->AddListener(this);
		
		// create the right horizontal arrow
		arrowInfo.left = mFrameSize.width - inRightIndent - arrowInfo.width;
		arrowInfo.bindings.left = false;
		arrowInfo.bindings.right = true;
		mDownRightArrow = MakeOneScrollArrow ( arrowInfo, kDownRight );								
		mDownRightArrow->AddListener(this);
		
	} // else view orientation is horizontal
	
	SetDefaultAttachable(this);		// Reset so Attachments don't get
									//   attached to the ScrollBars
									
} // MakeScrollArrows


//
// MakeOneScrollArrow
//
// Creates a scroll arrow in the given direction.
//
LControl*
CScrollerWithArrows :: MakeOneScrollArrow ( const SPaneInfo &inPaneInfo,
												ScrollDir inScrollWhichWay )
{
	ResIDT iconID = 0;
	SControlInfo controlInfo;
	controlInfo.value = 0;
	controlInfo.minValue = 0;
	controlInfo.maxValue = 0;
	
	if ( inScrollWhichWay == kUpLeft ) {
		controlInfo.valueMessage = kControlUpButtonPart;
		iconID = IsVertical() ? CScrollArrowControl::kIconUp : CScrollArrowControl::kIconLeft;
		
	}
	else {
		controlInfo.valueMessage = kControlDownButtonPart;
		iconID = IsVertical() ? CScrollArrowControl::kIconDown : CScrollArrowControl::kIconRight;
	}
	
	return new CScrollArrowControl ( inPaneInfo, controlInfo, iconID );
	
} // MakeOneScrollArrow


//
// FinishCreateSelf
//
// Finish creation of the scroller by installing its scrolling subview.
//
void
CScrollerWithArrows::FinishCreateSelf()
{
	LView* scrollingView = dynamic_cast<LView*>(FindPaneByID(mScrollingViewID));
	if (scrollingView != nil) {
		GrafPtr	myPort = GetMacPort();
		if (myPort == nil)
			myPort = UQDGlobals::GetCurrentPort();
		StVisRgn	suppressDrawing(myPort);
		InstallView(scrollingView);
	}
	
} // FinishCreateSelf


//
// ActivateSelf
//
// Show arrows that were hidden when app was deactivated
//
void
CScrollerWithArrows::ActivateSelf()
{
	if ( mUpLeftArrow )
		mUpLeftArrow->Show();
	
	if ( mDownRightArrow )
		mDownRightArrow->Show();

} // ActivateSelf


//
// DeactivateSelf
//
// According to Mac Human Interface Guidelines, ScrollBars in inactive
// windows are hidden. We follow this by hiding the scroll arrows.
//
void
CScrollerWithArrows::DeactivateSelf()
{
	if ( mUpLeftArrow && mUpLeftArrow->IsVisible() ) {
		mUpLeftArrow->Hide();
		mUpLeftArrow->DontRefresh(true);
	}
	
	if ( mDownRightArrow && mDownRightArrow->IsVisible() ) {
		mDownRightArrow->Hide();
		mDownRightArrow->DontRefresh(true);
	}
	
	if ( FocusExposed() ) {				// Redraw now!
		Rect frame;
		CalcLocalFrameRect(frame);
		if (ExecuteAttachments(msg_DrawOrPrint, &frame)) {
			DrawSelf();
		}
	}

} // DeactivateSelf


//
// InstallView
//
// Install a Scrolling View within this Scroller
//
void
CScrollerWithArrows::InstallView ( LView *inScrollingView )
{
	mScrollingView = inScrollingView;
	AdjustScrollArrows();
	
} // InstallView


//
// ExpandSubPane
//
// Expand a SubPane, which should be the Scroller's ScrollingView, to
// fill the interior of a Scroller
//
void
CScrollerWithArrows::ExpandSubPane ( LPane *inSub, Boolean inExpandHoriz,
										Boolean	inExpandVert )
{
	SDimension16 subSize;
	inSub->GetFrameSize(subSize);
	SPoint32 subLocation;
	inSub->GetFrameLocation(subLocation);

	if (inExpandHoriz) {
		subSize.width = mFrameSize.width - 2;
		if ( !IsVertical() )
			subSize.width -= 2 * 15;		// account for 2 scroll arrows
		subLocation.h = 1;
	} else
		subLocation.h -= mFrameLocation.h;
	
	if (inExpandVert) {
		subSize.height = mFrameSize.height - 2;
		if ( IsVertical() )
			subSize.height -= 2 * 15;		// account for 2 scroll arrows
		subLocation.v = 1;
	} else
		subLocation.v -= mFrameLocation.v;
	
	inSub->PlaceInSuperFrameAt(subLocation.h, subLocation.v, false);
	inSub->ResizeFrameTo(subSize.width, subSize.height, false);

} // ExpandSubPane


//
// AdjustScrollArrows
//
// Adjust the arrows (value, min, and max) according to the current
// state of the Scroller and ScrollingView
//
void
CScrollerWithArrows::AdjustScrollArrows()
{
	// nothing to scroll, bail out after disabling scrollarrows.
	if ( !mScrollingView ) {
		if ( mUpLeftArrow )
			mUpLeftArrow->SetValue(0);
	
		if ( mDownRightArrow )
			mDownRightArrow->SetMinValue(0);
		
		return;
	}

	SPoint32		scrollUnit;
	SDimension16	scrollFrameSize;
	SDimension32	scrollImageSize;
	SPoint32		scrollPosition;
	mScrollingView->GetScrollUnit(scrollUnit);
	mScrollingView->GetFrameSize(scrollFrameSize);
	mScrollingView->GetImageSize(scrollImageSize);
	mScrollingView->GetScrollPosition(scrollPosition);
	
	bool enable = false;
	if ( IsVertical() ) {
		Int32 vertDiff = scrollImageSize.height - scrollFrameSize.height;
		if (scrollPosition.v > vertDiff)
			vertDiff = scrollPosition.v;

		// if there is a reason to scroll, then enable the scroll arrows
		if (vertDiff > 0)
			enable = true;
	}
	else {
		Int32	horizDiff = scrollImageSize.width - scrollFrameSize.width;
		if (scrollPosition.h > horizDiff)
			horizDiff = scrollPosition.h;

		// if there is a reason to scroll, then enable the scroll arrows
		if (horizDiff > 0)
			enable = true;
	}
	if ( enable ) {
		mUpLeftArrow->Enable();
		mDownRightArrow->Enable();
	}
	else {
		mUpLeftArrow->Disable();
		mDownRightArrow->Disable();
	}			
	
} // AdjustScrollArrows


//
// ResizeFrameBy
//
// Adjust the framesize by the given ammounts and adjust the scroll arrows to reflect
// the new sizes.
//
void
CScrollerWithArrows::ResizeFrameBy( Int16 inWidthDelta, Int16 inHeightDelta, Boolean inRefresh )
{
	// Let LView do all the work. All Scroller has to do is
	// adjust the ScrollBars to account for the new size
	// of the Scroller and resize its Image so it matches
	// its Frame size.		
	LView::ResizeFrameBy(inWidthDelta, inHeightDelta, inRefresh);
	
	{		
		// Prevent scroll bars from drawing while adjusting them
		StVisRgn	suppressDrawing(GetMacPort());
		AdjustScrollArrows();
	}
	
	ResizeImageBy(inWidthDelta, inHeightDelta, false);

} // ResizeFrameBy


//
// SubImageChanged
//
// arrow settings depend on the ScrollingView Image, so adjust them
// to match the current state.
//
void
CScrollerWithArrows::SubImageChanged ( LView *inSubView )
{
	if (inSubView == mScrollingView)
		AdjustScrollArrows();

} // SubImageChanged


//
// ListenToMessage
//
// Unlike LScroller which just listens for the thumb changing message, this scroller
// uses messages for all aspects of scrolling (of course, there is no such thing as
// a thumb in this case).
//
void
CScrollerWithArrows::ListenToMessage( MessageT inMessage, void *ioParam)
{
	Int32 scrollUnits = 0;
	
	switch ( inMessage ) {

		case kControlUpButtonPart:		// Scroll up/left one unit
			scrollUnits = -1;
			break;
			
		case kControlDownButtonPart:	// Scroll down/right one unit
			scrollUnits = 1;
			break;
		
	} // case of which button clicked
		
	if ( scrollUnits ) {
		SPoint32 scrollUnit;
		Int32 scrollBy;
		
		mScrollingView->GetScrollUnit(scrollUnit);
		scrollBy = scrollUnits * (IsVertical() ? scrollUnit.v : scrollUnit.h);
		mScrollingView->ScrollPinnedImageBy(0, scrollBy, true);
	}
	
} // ListenToMessage


//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------

#pragma mark -- class CScrollArrowControl

CScrollArrowControl :: CScrollArrowControl (  const SPaneInfo &inPaneInfo,
												const SControlInfo	&inControlInfo,
												ResIDT inIconResID )
	: LGAIconButton ( inPaneInfo, inControlInfo, controlMode_Button, inIconResID,
						 sizeSelector_SmallIconSize, iconPosition_Center )
{
	// nothing else needed
}


CScrollArrowControl :: CScrollArrowControl ( LStream* inStream ) 
	:LGAIconButton(inStream)
{
	// nothing else needed	
}


//
// HotSpotAction
//
// Do the normal hot-spot stuff, but also send the message that the click is still going on,
// which effectively does auto-scroll. It is a bit too fast for my taste, but it works.
//
void
CScrollArrowControl :: HotSpotAction ( Int16 inHotSpot, Boolean inCurrInside, Boolean inPrevInside )
{
	LGAIconButton::HotSpotAction ( inHotSpot, inCurrInside, inPrevInside );
	if ( inCurrInside )
		BroadcastValueMessage();

} // HotSpotAction


//
// DrawSelf
//
// While the cursor tracking, etc of LGAIconButton is great, we don't want a border around
// the scroll triangle (it just looks bad). Only paint the background and draw the icon
//
void
CScrollArrowControl :: DrawSelf ( )
{
	StColorPenState::Normalize ();

	// Get the frame for the control and paint it
	Rect localFrame;
	CalcLocalFrameRect ( localFrame );
	localFrame.right--;
	::RGBForeColor ( &UGAColorRamp::GetColor(2));
	::PaintRect ( &localFrame );

	DrawIcon();

} // DrawSelf