/*
 * Copyright (c) 1999-2000, Eric Moon.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions, and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
 
// ValControl.cpp
 
#include "ValControl.h"
#include "ValControlSegment.h"
 
#include "TextControlFloater.h"
 
#include <Debug.h>
#include <String.h>
#include <Window.h>
 
#include <algorithm>
#include <functional>
#include <cstdio>
 
using namespace std;
 
__USE_CORTEX_NAMESPACE
 
 
const float ValControl::fSegmentPadding = 2.0;
 
// the decimal point covers one more pixel x and y-ward:
const float ValControl::fDecimalPointWidth = 2.0;
const float ValControl::fDecimalPointHeight = 2.0;
 
 
/*protected*/
ValControl::ValControl(BRect frame, const char* name, const char* label,
		BMessage* message, align_mode alignMode, align_flags alignFlags,
		update_mode updateMode, bool backBuffer)
	: BControl(frame, name, label, message, B_FOLLOW_TOP|B_FOLLOW_LEFT,
		B_WILL_DRAW|B_FRAME_EVENTS),
	fDirty(true),
	fUpdateMode(updateMode),
	fLabelFont(be_bold_font),
	fValueFont(be_bold_font),
	fAlignMode(alignMode),
	fAlignFlags(alignFlags),
	fOrigBounds(Bounds()),
	fHaveBackBuffer(backBuffer),
	fBackBuffer(NULL),
	fBackBufferView(NULL)
{
	if (fHaveBackBuffer)
		_AllocBackBuffer(frame.Width(), frame.Height());
 
//	m_font.SetSize(13.0);	
//	rgb_color red = {255,0,0,255};
//	SetViewColor(red);
}
 
 
ValControl::~ValControl()
{
	delete fBackBuffer;
}
 
 
ValControl::update_mode
ValControl::updateMode() const
{
	return fUpdateMode;
}
 
 
void
ValControl::setUpdateMode(update_mode mode)
{
	fUpdateMode = mode;
}
 
 
const BFont*
ValControl::labelFont() const
{
	return &fLabelFont;
}
 
 
void
ValControl::setLabelFont(const BFont* labelFont)
{
	fLabelFont = labelFont;
	// inform label segments
	_InvalidateAll();
}	
 
 
const BFont*
ValControl::valueFont() const
{
	return &fValueFont;
}
 
 
void
ValControl::setValueFont(const BFont* valueFont)
{
	fValueFont = valueFont;
	
	// inform value segments
	for (int n = CountEntries(); n > 0; --n) {
		const ValCtrlLayoutEntry& e = _EntryAt(n-1);
		if (e.type != ValCtrlLayoutEntry::SEGMENT_ENTRY)
			continue;
			
		ValControlSegment* s = dynamic_cast<ValControlSegment*>(e.pView);
		ASSERT(s);
		s->SetFont(&fValueFont);
		s->fontChanged(&fValueFont);
	}
}
 
 
float
ValControl::baselineOffset() const
{
	font_height h;
	be_plain_font->GetHeight(&h);
	return ceil(h.ascent);
}
 
 
float
ValControl::segmentPadding() const
{
	return fSegmentPadding;
}
 
 
BView*
ValControl::backBufferView() const
{
	return fBackBufferView;
}
 
 
BBitmap*
ValControl::backBuffer() const
{
	return fBackBuffer;
}
 
 
void
ValControl::dump()
{
#if defined(DEBUG)
	BRect f = Frame();
 
	PRINT((
		"*** ValControl::dump():\n"
		"    FRAME    (%.1f,%.1f)-(%.1f,%.1f)\n"
		"    ENTRIES:\n",
		f.left, f.top, f.right, f.bottom));
 
	for (layout_set::const_iterator it = fLayoutSet.begin();
		it != fLayoutSet.end(); ++it) {
		const ValCtrlLayoutEntry& e = *it;
		switch (e.type) {
			case ValCtrlLayoutEntry::SEGMENT_ENTRY:
				PRINT(("    Segment       "));
				break;
 
			case ValCtrlLayoutEntry::VIEW_ENTRY:
				PRINT(("    View          "));
				break;
 
			case ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY:
				PRINT(("    Decimal Point "));
				break;
 
			default:
				PRINT(("    ???           "));
				break;
		}
 
		PRINT(("\n      cached frame (%.1f,%.1f)-(%.1f,%.1f) + pad(%.1f)\n",
			e.frame.left, e.frame.top, e.frame.right, e.frame.bottom,
			e.fPadding));
 
		if (e.type == ValCtrlLayoutEntry::SEGMENT_ENTRY
			|| e.type == ValCtrlLayoutEntry::VIEW_ENTRY) {
			if (e.pView) {
				PRINT(("      real frame   (%.1f,%.1f)-(%.1f,%.1f)\n\n",
					e.pView->Frame().left, e.pView->Frame().top,
					e.pView->Frame().right, e.pView->Frame().bottom));
			} else
				PRINT(("      (no view!)\n\n"));
		}
	}
	PRINT(("\n"));
#endif
}
 
 
void
ValControl::SetEnabled(bool enabled)
{
	// redraw if enabled-state changes
	_Inherited::SetEnabled(enabled);
 
	_InvalidateAll();
}
 
 
void
ValControl::_InvalidateAll()
{
	Invalidate();
	int c = CountChildren();
	for (int n = 0; n < c; ++n)
		ChildAt(n)->Invalidate();
}
 
 
void
ValControl::AttachedToWindow()
{
	// adopt parent view's color
	if (Parent())
		SetViewColor(Parent()->ViewColor());
}
 
 
void
ValControl::AllAttached()
{
	// move children to requested positions
	BWindow* pWnd = Window();
	pWnd->BeginViewTransaction();
	
	for_each(fLayoutSet.begin(), fLayoutSet.end(),
		ptr_fun(&ValCtrlLayoutEntry::InitChildFrame)); // +++++?
	
	pWnd->EndViewTransaction();
}
 
 
//! Paint decorations (& decimal point)
void
ValControl::Draw(BRect updateRect)
{	
	// draw lightweight entries:
	for (unsigned int n = 0; n < fLayoutSet.size(); n++) {
		if (fLayoutSet[n].type == ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY)
			drawDecimalPoint(fLayoutSet[n]);
	}
}
 
 
void
ValControl::drawDecimalPoint(ValCtrlLayoutEntry& e)
{	
	rgb_color dark = {0, 0, 0, 255};
	rgb_color med = {200, 200, 200, 255};
//	rgb_color light = {244,244,244,255};
	
	BPoint center;
	center.x = e.frame.left + 1;
	center.y = baselineOffset() - 1;
	
	SetHighColor(dark);
	StrokeLine(center, center);
	SetHighColor(med);
	StrokeLine(center - BPoint(0, 1), center + BPoint(1, 0));
	StrokeLine(center - BPoint(1, 0), center + BPoint(0, 1));
 
//	SetHighColor(light);
//	StrokeLine(center+BPoint(-1,1), center+BPoint(-1,1));
//	StrokeLine(center+BPoint(1,1), center+BPoint(1,1));
//	StrokeLine(center+BPoint(-1,-1), center+BPoint(-1,-1));
//	StrokeLine(center+BPoint(1,-1), center+BPoint(1,-1));
}
 
 
void
ValControl::FrameResized(float width, float height)
{	
	_Inherited::FrameResized(width,height);
	if (fHaveBackBuffer)
		_AllocBackBuffer(width, height);
//
//	PRINT((
//		"# ValControl::FrameResized(): %.1f, %.1f\n",
//		width, height));
}
 
 
void
ValControl::GetPreferredSize(float* outWidth, float* outHeight)
{
	ASSERT(fLayoutSet.size() > 0);
	
	*outWidth =
		fLayoutSet.back().frame.right -
		fLayoutSet.front().frame.left;
		
	*outHeight = 0;
	for(layout_set::const_iterator it = fLayoutSet.begin();
		it != fLayoutSet.end(); ++it) {
		if((*it).frame.Height() > *outHeight)
			*outHeight = (*it).frame.Height();
	}
//
//	PRINT((
//		"# ValControl::GetPreferredSize(): %.1f, %.1f\n",
//		*outWidth, *outHeight));
}
 
 
void
ValControl::MakeFocus(bool focused)
{
	_Inherited::MakeFocus(focused);
 
	// +++++ only the underline needs to be redrawn	
	_InvalidateAll();
}
 
 
void
ValControl::MouseDown(BPoint where)
{
	MakeFocus(true);
}
 
 
void
ValControl::MessageReceived(BMessage* message)
{
	status_t err;
	const char* stringValue;
 
//	PRINT((
//		"ValControl::MessageReceived():\n"));
//	message->PrintToStream();
 
	switch (message->what) {
		case M_SET_VALUE:
			err = message->FindString("_value", &stringValue);
			if(err < B_OK) {
				PRINT((
					"! ValControl::MessageReceived(): no _value found!\n"));
				break;
			}
				
			// set from string
			err = setValueFrom(stringValue);
			if (err < B_OK) {
				PRINT((
					"! ValControl::MessageReceived(): setValueFrom('%s'):\n"
					"  %s\n",
					stringValue,
					strerror(err)));
			}
			
			// +++++ broadcast new value +++++ [23aug99]
			break;
			
		case M_GET_VALUE: // +++++
			break;
			
		default:
			_Inherited::MessageReceived(message);
	}
}
 
 
// -------------------------------------------------------- //
// archiving/instantiation
// -------------------------------------------------------- //
 
ValControl::ValControl(BMessage* archive)
	: BControl(archive),
	fDirty(true)
{
	// fetch parameters
	archive->FindInt32("updateMode", (int32*)&fUpdateMode);
	archive->FindInt32("alignMode", (int32*)&fAlignMode);
	archive->FindInt32("alignFlags", (int32*)&fAlignFlags);
 
	// original bounds
	archive->FindRect("origBounds", &fOrigBounds);
}
 
 
status_t
ValControl::Archive(BMessage* archive, bool deep) const
{
	status_t err = _Inherited::Archive(archive, deep);
	
	// write parameters
	if (err == B_OK)
		err = archive->AddInt32("updateMode", (int32)fUpdateMode);
	if (err == B_OK)
		err = archive->AddInt32("alignMode", (int32)fAlignMode);
	if (err == B_OK)
		err = archive->AddInt32("alignFlags", (int32)fAlignFlags);
	if (err == B_OK)
		err = archive->AddRect("origBounds", fOrigBounds);
	if (err < B_OK)
		return err;
 
	// write layout set?
	if (!deep)
		return B_OK;
 
	// yes; spew it:
	for (layout_set::const_iterator it = fLayoutSet.begin();
		it != fLayoutSet.end(); it++) {
 
		// archive entry
		BMessage layoutSet;
		ASSERT((*it).pView);
		err = (*it).pView->Archive(&layoutSet, true);
		ASSERT(err == B_OK);
		
		// write it
		archive->AddMessage("layoutSet", &layoutSet);
	}
 
	return B_OK;
}
 
 
// -------------------------------------------------------- //
// internal operations
// -------------------------------------------------------- //
 
// add segment view (which is responsible for generating its
// own ValCtrlLayoutEntry)
void
ValControl::_Add(ValControlSegment* segment, entry_location from,
	uint16 distance)
{
	BWindow* pWnd = Window();
	if(pWnd)
		pWnd->BeginViewTransaction();
	
	AddChild(segment);
	
	segment->SetFont(&fValueFont);
	segment->fontChanged(&fValueFont);
	
	uint16 nIndex = _LocationToIndex(from, distance);
	ValCtrlLayoutEntry entry = segment->makeLayoutEntry();
	_InsertEntry(entry, nIndex);
//	linkSegment(segment, nIndex);
	
	if (pWnd)
		pWnd->EndViewTransaction();
}
 
 
// add general view (manipulator, label, etc.)
// the entry's frame rectangle will be filled in
void
ValControl::_Add(ValCtrlLayoutEntry& entry, entry_location from)
{
	BWindow* window = Window();
	if (window)
		window->BeginViewTransaction();
 
	if (entry.pView)	
		AddChild(entry.pView);
 
	uint16 index = _LocationToIndex(from, 0);
	_InsertEntry(entry, index);
 
	if (window)
		window->EndViewTransaction();
}
 
 
// access child-view ValCtrlLayoutEntry
// (_IndexOf returns index from left)
const ValCtrlLayoutEntry&
ValControl::_EntryAt(entry_location from, uint16 distance) const
{
	uint16 nIndex = _LocationToIndex(from, distance);
	ASSERT(nIndex < fLayoutSet.size());
	return fLayoutSet[nIndex];
}
 
 
const ValCtrlLayoutEntry&
ValControl::_EntryAt(uint16 offset) const
{
	uint16 nIndex = _LocationToIndex(FROM_LEFT, offset);
	ASSERT(nIndex < fLayoutSet.size());
	return fLayoutSet[nIndex];
}
 
 
uint16
ValControl::_IndexOf(BView* child) const
{
	for (uint16 n = 0; n < fLayoutSet.size(); n++) {
		if (fLayoutSet[n].pView == child)
			return n;
	}
 
	ASSERT(!"shouldn't be here");
	return 0;
}
 
 
uint16
ValControl::CountEntries() const
{
	return fLayoutSet.size();
}
 
 
// pop up keyboard input field +++++
void
ValControl::showEditField()
{
	BString valueString;
 
#if defined(DEBUG)
	status_t err = getString(valueString);
	ASSERT(err == B_OK);
#endif	// DEBUG
	
	BRect f = Bounds().OffsetByCopy(4.0, -4.0);
	ConvertToScreen(&f);
	//PRINT((
	//"# ValControl::showEditField(): base bounds (%.1f, %.1f)-(%.1f,%.1f)\n",
	//f.left, f.top, f.right, f.bottom));
	
	new TextControlFloater(f, B_ALIGN_RIGHT, &fValueFont, valueString.String(),
		BMessenger(this), new BMessage(M_SET_VALUE));
		// TextControlFloater embeds new value
		// in message: _value (string) +++++ DO NOT HARDCODE
}
 
 
//! (Re-)initialize backbuffer
void
ValControl::_AllocBackBuffer(float width, float height)
{
	ASSERT(fHaveBackBuffer);
	if (fBackBuffer && fBackBuffer->Bounds().Width() >= width
		&& fBackBuffer->Bounds().Height() >= height)
		return;
 
	if (fBackBuffer) {
		delete fBackBuffer;
		fBackBuffer = NULL;
		fBackBufferView = NULL;
	}
 
	BRect bounds(0, 0, width, height);
	fBackBuffer = new BBitmap(bounds, B_RGB32, true);
	fBackBufferView = new BView(bounds, "back", B_FOLLOW_NONE, B_WILL_DRAW);
	fBackBuffer->AddChild(fBackBufferView);
}
 
 
// ref'd view must already be a child +++++
// (due to GetPreferredSize implementation in segment views)
void
ValControl::_InsertEntry(ValCtrlLayoutEntry& entry, uint16 index)
{
	// view ptr must be 0, or a ValControlSegment that's already a child
	ValControlSegment* pSeg = dynamic_cast<ValControlSegment*>(entry.pView);
	if (entry.pView)
		ASSERT(pSeg);
	if (pSeg)
		ASSERT(this == pSeg->Parent());
 
	// entry must be at one side or the other:
	ASSERT(!index || index == fLayoutSet.size());
 
	// figure padding
	bool bNeedsPadding =
		!(entry.flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING ||
		 ((index - 1 >= 0 &&
		  fLayoutSet[index - 1].flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING)) ||
		 ((index + 1 < static_cast<uint16>(fLayoutSet.size()) &&
		  fLayoutSet[index + 1].flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING)));		  
 
	entry.fPadding = (bNeedsPadding) ? fSegmentPadding : 0.0;		
 
	// fetch (and grant) requested frame size
	BRect frame(0, 0, 0, 0);
	if (pSeg)
		pSeg->GetPreferredSize(&frame.right, &frame.bottom);
	else
		_GetDefaultEntrySize(entry.type, &frame.right, &frame.bottom);
 
	// figure amount this entry will displace:
	float fDisplacement = frame.Width() + entry.fPadding + 1;
 
	// set entry's top-left position:
	if (!fLayoutSet.size()) {
		// sole entry:
		if (fAlignMode == ALIGN_FLUSH_RIGHT)
			frame.OffsetBy(Bounds().right - frame.Width(), 0.0);
	} else if (index) {
		// insert at right side
		if (fAlignMode == ALIGN_FLUSH_LEFT)
			frame.OffsetBy(fLayoutSet.back().frame.right + 1 + entry.fPadding, 0.0);
		else
			frame.OffsetBy(fLayoutSet.back().frame.right - frame.Width(), 0.0); //+++++
	} else {
		// insert at left side
		if (fAlignMode == ALIGN_FLUSH_RIGHT)
			frame.OffsetBy(fLayoutSet.front().frame.left - fDisplacement, 0.0);
	}
 
	// add to layout set
	entry.frame = frame;
	fLayoutSet.insert(
		index ? fLayoutSet.end() : fLayoutSet.begin(),
		entry);
 
	// slide following or preceding entries (depending on align mode)
	// to make room:
	switch (fAlignMode) {
		case ALIGN_FLUSH_LEFT:
			// following entries are shifted to the right
			for(uint32 n = index+1; n < fLayoutSet.size(); n++)
				_SlideEntry(n, fDisplacement);
			break;
			
		case ALIGN_FLUSH_RIGHT: {
			// preceding entries are shifted to the left
			for(int n = index-1; n >= 0; n--)
				_SlideEntry(n, -fDisplacement);
 
			break;
		}
	}
//	
//	PRINT((
//		"### added entry: (%.1f,%.1f)-(%.1f,%.1f)\n",
//		frame.left, frame.top, frame.right, frame.bottom));
}
 
 
void
ValControl::_SlideEntry(int index, float delta)
{
	ValCtrlLayoutEntry& e = fLayoutSet[index];
	e.frame.OffsetBy(delta, 0.0);
 
	// move & possibly resize view:
	if (e.pView) {
		e.pView->MoveTo(e.frame.LeftTop());
 
		BRect curFrame = e.pView->Frame();
		float fWidth = e.frame.Width();
		float fHeight = e.frame.Height();
		if (curFrame.Width() != fWidth
			|| curFrame.Height() != fHeight)
			e.pView->ResizeTo(fWidth + 5.0, fHeight);
	}
}
 
 
uint16
ValControl::_LocationToIndex(entry_location from, uint16 distance) const
{
	uint16 nResult = 0;
 
	switch (from) {
		case FROM_LEFT:
			nResult = distance;
			break;
 
		case FROM_RIGHT:
			nResult = fLayoutSet.size() - distance;
			break;
	}
 
	ASSERT(nResult <= fLayoutSet.size());
	return nResult;
}
 
 
void
ValControl::_GetDefaultEntrySize(ValCtrlLayoutEntry::entry_type type,
	float* outWidth, float* outHeight)
{
	switch (type) {
		case ValCtrlLayoutEntry::SEGMENT_ENTRY:
		case ValCtrlLayoutEntry::VIEW_ENTRY:
			*outWidth = 1.0;
			*outHeight = 1.0;
			break;
 
		case ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY:
			*outWidth = fDecimalPointWidth;
			*outHeight = fDecimalPointHeight;
			break;
	}
}

V757 It is possible that an incorrect variable is compared with nullptr after type conversion using 'dynamic_cast'. Check lines: 600, 601.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fHaveBackBuffer, fBackBuffer, fBackBufferView.