/*
 * 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.
 */
 
 
// InfoView.cpp
 
#include "InfoView.h"
#include "cortex_ui.h"
 
#include "array_delete.h"
 
// Interface Kit
#include <Bitmap.h>
#include <Region.h>
#include <ScrollBar.h>
#include <StringView.h>
#include <TextView.h>
#include <Window.h>
// Storage Kit
#include <Mime.h>
// Support Kit
#include <List.h>
 
__USE_CORTEX_NAMESPACE
 
#include <Debug.h>
#define D_ALLOC(X) //PRINT (x)			// ctor/dtor
#define D_HOOK(X) //PRINT (x)			// BView impl.
#define D_ACCESS(X) //PRINT (x)			// Accessors
#define D_METHOD(x) //PRINT (x)
 
// -------------------------------------------------------- //
// *** internal class: _InfoTextField
//
// * PURPOSE:
//   store the label & text for each field, and provide methods
//   for linewrapping and drawing
//
// -------------------------------------------------------- //
 
class _InfoTextField
{
 
public:					// *** ctor/dtor
 
						_InfoTextField(
							BString label,
							BString text,
							InfoView *parent);
 
						~_InfoTextField();
 
public:					// *** operations
 
	void				drawField(
							BPoint position);
 
	void				updateLineWrapping(
							bool *wrappingChanged = 0,
							bool *heightChanged = 0);
 
public:					// *** accessors
 
	float				getHeight() const;
 
	float				getWidth() const;
 
	bool				isWrapped() const;
 
private:				// *** static internal methods
 
	static bool			canEndLine(
							const char c);
 
	static bool			mustEndLine(
							const char c);
 
private:				// *** data members
 
	BString				m_label;
	
	BString				m_text;
 
	BList			   *m_textLines;
 
	InfoView		   *m_parent;
};
 
// -------------------------------------------------------- //
// *** static member init
// -------------------------------------------------------- //
 
const BRect InfoView::M_DEFAULT_FRAME	= BRect(0.0, 0.0, 250.0, 100.0);
const float InfoView::M_H_MARGIN		= 5.0;
const float InfoView::M_V_MARGIN		= 5.0;
 
// -------------------------------------------------------- //
// *** ctor/dtor (public)
// -------------------------------------------------------- //
 
InfoView::InfoView(
	BString title,
	BString subTitle,
	BBitmap *icon)
	: BView(M_DEFAULT_FRAME, "InfoView", B_FOLLOW_ALL_SIDES,
			B_WILL_DRAW | B_FRAME_EVENTS),
	  m_title(title),
	  m_subTitle(subTitle),
	  m_icon(0),
	  m_fields(0) {
	D_ALLOC(("InfoView::InfoView()\n"));
 
	if (icon) {
		m_icon = new BBitmap(icon);
	}
	m_fields = new BList();
	SetViewColor(B_TRANSPARENT_COLOR);
}
 
InfoView::~InfoView() {
	D_ALLOC(("InfoView::~InfoView()\n"));
 
	// delete all the fields
	if (m_fields) {
		while (m_fields->CountItems() > 0) {
			_InfoTextField *field = static_cast<_InfoTextField *>
									(m_fields->RemoveItem((int32)0));
			if (field) {
				delete field;
			}
		}
		delete m_fields;
		m_fields = 0;
	}
 
	// delete the icon bitmap
	if (m_icon) {
		delete m_icon;
		m_icon = 0;
	}
}
 
// -------------------------------------------------------- //
// *** BView implementation
// -------------------------------------------------------- //
 
void InfoView::AttachedToWindow() {
	D_HOOK(("InfoView::AttachedToWindow()\n"));
 
	// adjust the windows title
	BString title = m_title;
	title << " info";
	Window()->SetTitle(title.String());
	
	// calculate the area occupied by title, subtitle and icon
	font_height fh;
	be_bold_font->GetHeight(&fh);
	float titleHeight = fh.leading + fh.descent;
	titleHeight += M_V_MARGIN * 2.0 + B_LARGE_ICON / 2.0;
	be_plain_font->GetHeight(&fh);
	titleHeight += fh.leading + fh.ascent + fh.descent;
	BFont font(be_bold_font);
	float titleWidth = font.StringWidth(title.String());
	titleWidth += M_H_MARGIN + B_LARGE_ICON + B_LARGE_ICON / 2.0;
 
	float width, height;
	GetPreferredSize(&width, &height);
	Window()->ResizeTo(width + B_V_SCROLL_BAR_WIDTH, height);
	ResizeBy(- B_V_SCROLL_BAR_WIDTH, 0.0);
 
	// add scroll bar
	BRect scrollRect = Window()->Bounds();
	scrollRect.left = scrollRect.right - B_V_SCROLL_BAR_WIDTH + 1.0;
	scrollRect.top -= 1.0;
	scrollRect.right += 1.0;
	scrollRect.bottom -= B_H_SCROLL_BAR_HEIGHT - 1.0;
	Window()->AddChild(new BScrollBar(scrollRect, "ScrollBar", this,
									  0.0, 0.0, B_VERTICAL));
	ScrollBar(B_VERTICAL)->SetRange(0.0, 0.0);
	be_plain_font->GetHeight(&fh);
	float step = fh.ascent + fh.descent + fh.leading + M_V_MARGIN;
	ScrollBar(B_VERTICAL)->SetSteps(step, step * 5);
									
	// set window size limits
	float minWidth, maxWidth, minHeight, maxHeight;
	Window()->GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
	Window()->SetSizeLimits(titleWidth + B_V_SCROLL_BAR_WIDTH, maxWidth,
							titleHeight + B_H_SCROLL_BAR_HEIGHT, maxHeight);
 
	// cache the bounds rect for proper redraw later on...
	m_oldFrame = Bounds();
}
 
void InfoView::Draw(
	BRect updateRect) {
	D_HOOK(("InfoView::Draw()\n"));
 
	// Draw side bar
	SetDrawingMode(B_OP_COPY);
	BRect r = Bounds();
	r.right = B_LARGE_ICON - 1.0;
	SetLowColor(M_LIGHT_BLUE_COLOR);
	FillRect(r, B_SOLID_LOW);
	SetHighColor(M_DARK_BLUE_COLOR);
	r.right += 1.0;
	StrokeLine(r.RightTop(), r.RightBottom(), B_SOLID_HIGH);
 
	// Draw background
	BRegion region;
	region.Include(updateRect);
	region.Exclude(r);
	SetLowColor(M_GRAY_COLOR);
	FillRegion(&region, B_SOLID_LOW);
 
	// Draw title
	SetDrawingMode(B_OP_OVER);
	font_height fh;
	be_bold_font->GetHeight(&fh);
	SetFont(be_bold_font);
	BPoint p(M_H_MARGIN + B_LARGE_ICON + B_LARGE_ICON / 2.0,
			 M_V_MARGIN * 2.0 + fh.ascent);
	SetHighColor(M_BLACK_COLOR);
	DrawString(m_title.String(), p);
	
	// Draw sub-title
	p.y += fh.descent;
	be_plain_font->GetHeight(&fh);
	SetFont(be_plain_font);
	p.y += fh.ascent + fh.leading;
	SetHighColor(M_DARK_GRAY_COLOR);
	DrawString(m_subTitle.String(), p);
 
	// Draw icon
	p.y = 2 * M_V_MARGIN;
	if (m_icon) {
		p.x = B_LARGE_ICON / 2.0;
		DrawBitmapAsync(m_icon, p);
	}
 
	// Draw fields
	be_plain_font->GetHeight(&fh);
	p.x = B_LARGE_ICON;
	p.y += B_LARGE_ICON + 2 * M_V_MARGIN + fh.ascent + 2 * fh.leading;
	for (int32 i = 0; i < m_fields->CountItems(); i++) {
		_InfoTextField *field = static_cast<_InfoTextField *>(m_fields->ItemAt(i));
		field->drawField(p);
		p.y += field->getHeight() + M_V_MARGIN;
	}
}
 
void InfoView::FrameResized(
	float width,
	float height) {
	D_HOOK(("InfoView::FrameResized()\n"));
 
	BRect newFrame = BRect(0.0, 0.0, width, height);
 
	// update the each lines' line-wrapping and redraw as necessary
	font_height fh;
	BPoint p;
	be_plain_font->GetHeight(&fh);
	p.x = B_LARGE_ICON;
	p.y += B_LARGE_ICON + M_V_MARGIN * 2.0 + fh.ascent + fh.leading * 2.0;
	bool heightChanged = false;
	for (int32 i = 0; i < m_fields->CountItems(); i++) {
		bool wrappingChanged = false;
		_InfoTextField *field = static_cast<_InfoTextField *>(m_fields->ItemAt(i));
		field->updateLineWrapping(&wrappingChanged,
								  heightChanged ? 0 : &heightChanged);
		float fieldHeight = field->getHeight() + M_V_MARGIN;
		if (heightChanged) {
			Invalidate(BRect(p.x, p.y, width, p.y + fieldHeight));
		}
		else if (wrappingChanged) {
			Invalidate(BRect(p.x + m_sideBarWidth, p.y, width, p.y + fieldHeight));
		}
		p.y += fieldHeight;
	}
 
	// clean up the rest of the view
	BRect updateRect;
	updateRect.left = B_LARGE_ICON;
	updateRect.top = p.y < (m_oldFrame.bottom - M_V_MARGIN - 15.0) ?
					 p.y - 15.0 : m_oldFrame.bottom - M_V_MARGIN - 15.0;
	updateRect.right = width - M_H_MARGIN;
	updateRect.bottom = m_oldFrame.bottom < newFrame.bottom ?
						newFrame.bottom : m_oldFrame.bottom;
	Invalidate(updateRect);
 
	if (p.y > height) {
		ScrollBar(B_VERTICAL)->SetRange(0.0, ceil(p.y - height));
	}
	else {
		ScrollBar(B_VERTICAL)->SetRange(0.0, 0.0);
	}
 
	// cache the new frame rect for the next time
	m_oldFrame = newFrame;
}
 
void
InfoView::GetPreferredSize(
	float *width,
	float *height) {
	D_HOOK(("InfoView::GetPreferredSize()\n"));
 
	*width = 0;
	*height = 0;
 
	// calculate the height needed to display everything, avoiding line wrapping
	font_height fh;
	be_plain_font->GetHeight(&fh);
	for (int32 i = 0; i < m_fields->CountItems(); i++) {
		_InfoTextField *field = static_cast<_InfoTextField *>(m_fields->ItemAt(i));
		*height += fh.ascent + fh.descent + fh.leading + M_V_MARGIN;
		float tfw = field->getWidth();
		if (tfw > *width) {
			*width = tfw;
		}
	}
 
	*width += B_LARGE_ICON + 2 * M_H_MARGIN;
	*height += B_LARGE_ICON + 2 * M_V_MARGIN + fh.ascent + 2 * fh.leading;
	*height += B_H_SCROLL_BAR_HEIGHT;
}
 
// -------------------------------------------------------- //
// *** operations (protected)
// -------------------------------------------------------- //
 
void InfoView::addField(
	BString label,
	BString text) {
	D_METHOD(("InfoView::addField()\n"));
 
	m_fields->AddItem(new _InfoTextField(label, text, this));
}
 
// -------------------------------------------------------- //
// *** internal class: _InfoTextField
//
// *** ctor/dtor
// -------------------------------------------------------- //
 
_InfoTextField::_InfoTextField(
	BString label,
	BString text,
	InfoView *parent)
	: m_label(label),
	  m_text(text),
	  m_textLines(0),
	  m_parent(parent) {
	D_ALLOC(("_InfoTextField::_InfoTextField()\n"));
 
	if (m_label != "") {
		m_label << ":  ";
	}
	m_textLines = new BList();
}
 
_InfoTextField::~_InfoTextField() {
	D_ALLOC(("_InfoTextField::~_InfoTextField()\n"));
 
	// delete every line
	if (m_textLines) {
		while (m_textLines->CountItems() > 0) {
			BString *line = static_cast<BString *>(m_textLines->RemoveItem((int32)0));
			if (line) {
				delete line;
			}
		}
		delete m_textLines;
		m_textLines = 0;
	}
}
 
// -------------------------------------------------------- //
// *** internal class: _InfoTextField
//
// *** operations (public)
// -------------------------------------------------------- //
 
void _InfoTextField::drawField(
	BPoint position) {
	D_METHOD(("_InfoTextField::drawField()\n"));
 
	float sideBarWidth = m_parent->getSideBarWidth();
 
	// Draw label
	BPoint p = position;
	p.x += sideBarWidth - be_plain_font->StringWidth(m_label.String());
	m_parent->SetHighColor(M_DARK_GRAY_COLOR);
	m_parent->SetDrawingMode(B_OP_OVER);
	m_parent->SetFont(be_plain_font);
	m_parent->DrawString(m_label.String(), p);
	
	// Draw text
	font_height fh;
	be_plain_font->GetHeight(&fh);
	p.x = position.x + sideBarWidth;// + InfoView::M_H_MARGIN;
	m_parent->SetHighColor(M_BLACK_COLOR);
	for (int32 i = 0; i < m_textLines->CountItems(); i++) {
		BString *line = static_cast<BString *>(m_textLines->ItemAt(i));
		m_parent->DrawString(line->String(), p);
		p.y += fh.ascent + fh.descent + fh.leading;
	}
}
 
void _InfoTextField::updateLineWrapping(
	bool *wrappingChanged,
	bool *heightChanged)
{
	D_METHOD(("_InfoTextField::updateLineWrapping()\n"));
	
	// clear the current list of lines but remember their number and
	// the number of characters per line (to know if something changed)
	int32 numLines = m_textLines->CountItems();
	int32* numChars = new int32[numLines];
	array_delete<int32> _d(numChars);
	
	for (int32 i = 0; i < numLines; i++)
	{
		BString *line = static_cast<BString *>(m_textLines->ItemAt(i));
		numChars[i] = line->CountChars();
		delete line;
	}
	m_textLines->MakeEmpty();
 
	// calculate the maximum width for a line
	float maxWidth = m_parent->Bounds().Width();
	maxWidth -= m_parent->getSideBarWidth() + 3 * InfoView::M_H_MARGIN + B_LARGE_ICON;
	if (maxWidth <= be_plain_font->StringWidth("M"))
	{
		return;
	}
 
	// iterate through the text and split into new lines as
	// necessary
	BString *currentLine = new BString(m_text);
	while (currentLine && (currentLine->CountChars() > 0))
	{
		int32 lastBreak = 0;
		for (int32 i = 0; i < currentLine->CountChars(); i++)
		{
			if (canEndLine(currentLine->ByteAt(i)))
			{
				lastBreak = i + 1;
				if (mustEndLine(currentLine->ByteAt(i)))
				{
					BString *newLine = new BString();
					currentLine->Remove(i, 1);
					currentLine->MoveInto(*newLine, i,
										  currentLine->CountChars() - i);
					m_textLines->AddItem(currentLine);
					currentLine = newLine;
					break;
				}
			}
			else
			{
				if (i == currentLine->CountChars() - 1) // the last char in the text
				{
					m_textLines->AddItem(currentLine);
					currentLine = 0;
					break;
				}
				else
				{
					BString buffer;
					currentLine->CopyInto(buffer, 0, i);
					if (be_plain_font->StringWidth(buffer.String()) > maxWidth)
					{
						if (lastBreak < 1)
						{
							lastBreak = i - 1;
						}
						BString *newLine = new BString();
						currentLine->MoveInto(*newLine, lastBreak,
											  currentLine->CountChars() - lastBreak);
						m_textLines->AddItem(currentLine);
						currentLine = newLine;
						break;
					}
				}
			}
		}
	}
 
	// report changes in the fields total height (i.e. if the number
	// of lines changed)
	if (heightChanged && (numLines != m_textLines->CountItems()))
	{
		*heightChanged = true;
	}
 
	// report changes in the wrapping (e.g. if a word slipped into the
	// next line)
	else if (wrappingChanged)
	{
		for (int32 i = 0; i < m_textLines->CountItems(); i++)
		{
			BString *line = static_cast<BString *>(m_textLines->ItemAt(i));
			if (line->CountChars() != numChars[i])
			{
				*wrappingChanged = true;
				break;
			}
		}
	}
}
 
// -------------------------------------------------------- //
// *** internal class: _InfoTextField
//
// *** accessors (public)
// -------------------------------------------------------- //
 
float
_InfoTextField::getHeight() const {
	D_ACCESS(("_InfoTextField::getHeight()\n"));
 
	font_height fh;
	be_plain_font->GetHeight(&fh);
 
	// calculate the width for an empty line (separator)
	if (m_textLines->CountItems() == 0)
	{
		return fh.ascent + fh.descent + fh.leading;
	}
 
	// calculate the total height of the field by counting the
	else
	{
		float height = fh.ascent + fh.descent + fh.leading;
		height *= m_textLines->CountItems();
		height += fh.leading;
		return height;
	}
}
 
float
_InfoTextField::getWidth() const {
	D_ACCESS(("_InfoTextField::getWidth()\n"));
 
	float width = be_plain_font->StringWidth(m_text.String());
	width += m_parent->getSideBarWidth();
 
	return width;
}
 
bool
_InfoTextField::isWrapped() const {
	D_ACCESS(("_InfoTextField::isWrapped()\n"));
 
	return (m_textLines->CountItems() > 1);
}
 
// -------------------------------------------------------- //
// *** internal class: _InfoTextField
//
// *** static internal methods (private)
// -------------------------------------------------------- //
 
bool _InfoTextField::canEndLine(
	const char c)
{
	D_METHOD(("_InfoTextField::canEndLine()\n"));
 
	if ((c == B_SPACE) || (c == B_TAB) || (c == B_ENTER)
	 || ( c == '-'))
	{
		return true;
	}
	return false;
}
 
bool _InfoTextField::mustEndLine(
	const char c)
{
	D_METHOD(("_InfoTextField::mustEndLine()\n"));
 
	if (c == B_ENTER)
	{
		return true;
	}
	return false;
}
 
// END -- InfoView.cpp --

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: m_sideBarWidth.