⛏️ index : haiku.git

/*
 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT license.
 */

#include "HyperTextView.h"

#include <Cursor.h>
#include <Message.h>
#include <Region.h>
#include <Window.h>

#include <ObjectList.h>


// #pragma mark - HyperTextAction


HyperTextAction::HyperTextAction()
{
}


HyperTextAction::~HyperTextAction()
{
}


void
HyperTextAction::MouseOver(HyperTextView* view, BPoint where, int32 startOffset,
	int32 endOffset, BMessage* message)
{
	BCursor linkCursor(B_CURSOR_ID_FOLLOW_LINK);
	view->SetViewCursor(&linkCursor);

	BFont font;
	view->GetFont(&font);
	font.SetFace(B_UNDERSCORE_FACE);
	view->SetFontAndColor(startOffset, endOffset, &font, B_FONT_FACE);
}


void
HyperTextAction::MouseAway(HyperTextView* view, BPoint where, int32 startOffset,
	int32 endOffset, BMessage* message)
{
	BCursor linkCursor(B_CURSOR_ID_SYSTEM_DEFAULT);
	view->SetViewCursor(&linkCursor);

	BFont font;
	view->GetFont(&font);
	font.SetFace(B_REGULAR_FACE);
	view->SetFontAndColor(startOffset, endOffset, &font, B_FONT_FACE);
}


void
HyperTextAction::Clicked(HyperTextView* view, BPoint where, BMessage* message)
{
}


// #pragma mark - HyperTextView


struct HyperTextView::ActionInfo {
	ActionInfo(int32 startOffset, int32 endOffset, HyperTextAction* action)
		:
		startOffset(startOffset),
		endOffset(endOffset),
		action(action)
	{
	}

	~ActionInfo()
	{
		delete action;
	}

	static int Compare(const ActionInfo* a, const ActionInfo* b)
	{
		return a->startOffset - b->startOffset;
	}

	static int CompareEqualIfIntersecting(const ActionInfo* a,
		const ActionInfo* b)
	{
		if (a->startOffset < b->endOffset && b->startOffset < a->endOffset)
			return 0;
		return a->startOffset - b->startOffset;
	}

	int32				startOffset;
	int32				endOffset;
	HyperTextAction*	action;
};



class HyperTextView::ActionInfoList
	: public BObjectList<HyperTextView::ActionInfo, true> {
public:
	ActionInfoList(int32 itemsPerBlock = 20)
		: BObjectList<HyperTextView::ActionInfo, true>(itemsPerBlock)
	{
	}
};


HyperTextView::HyperTextView(const char* name, uint32 flags)
	:
	BTextView(name, flags),
	fActionInfos(new ActionInfoList(100)),
	fLastActionInfo(NULL)
{
}


HyperTextView::HyperTextView(BRect frame, const char* name, BRect textRect,
	uint32 resizeMask, uint32 flags)
	:
	BTextView(frame, name, textRect, resizeMask, flags),
	fActionInfos(new ActionInfoList(100)),
	fLastActionInfo(NULL)
{
}


HyperTextView::~HyperTextView()
{
	delete fActionInfos;
}


void
HyperTextView::MouseDown(BPoint where)
{
	// We eat all mouse button events.

	BTextView::MouseDown(where);
}


void
HyperTextView::MouseUp(BPoint where)
{
	BMessage* message = Window()->CurrentMessage();

	HyperTextAction* action = _ActionAt(where);
	if (action != NULL)
		action->Clicked(this, where, message);

	BTextView::MouseUp(where);
}


void
HyperTextView::MouseMoved(BPoint where, uint32 transit,
	const BMessage* dragMessage)
{
	BMessage* message = Window()->CurrentMessage();

	HyperTextAction* action;
	const ActionInfo* actionInfo = _ActionInfoAt(where);
	if (actionInfo != fLastActionInfo) {
		// We moved to a different "action" zone, de-highlight the previous one
		if (fLastActionInfo != NULL) {
			action = fLastActionInfo->action;
			if (action != NULL) {
				action->MouseAway(this, where, fLastActionInfo->startOffset,
						fLastActionInfo->endOffset, message);
			}
		}

		// ... and highlight the new one
		if (actionInfo != NULL) {
			action = actionInfo->action;
			if (action != NULL) {
				action->MouseOver(this, where, actionInfo->startOffset,
						actionInfo->endOffset, message);
			}
		}

		fLastActionInfo = actionInfo;
	}

	int32 buttons = 0;
	message->FindInt32("buttons", (int32*)&buttons);
	if (actionInfo == NULL || buttons != 0) {
		// This will restore the default mouse pointer, so do it only when not
		// hovering a link, or when clicking
		BTextView::MouseMoved(where, transit, dragMessage);
	}
}


void
HyperTextView::AddHyperTextAction(int32 startOffset, int32 endOffset,
	HyperTextAction* action)
{
	if (action == NULL || startOffset >= endOffset) {
		delete action;
		return;
	}

	fActionInfos->BinaryInsert(new ActionInfo(startOffset, endOffset, action),
		ActionInfo::Compare);

	// TODO: Of course we should check for overlaps...
}


void
HyperTextView::InsertHyperText(const char* inText, HyperTextAction* action,
	const text_run_array* inRuns)
{
	int32 startOffset = TextLength();
	Insert(inText, inRuns);
	int32 endOffset = TextLength();

	AddHyperTextAction(startOffset, endOffset, action);
}


void
HyperTextView::InsertHyperText(const char* inText, int32 inLength,
	HyperTextAction* action, const text_run_array* inRuns)
{
	int32 startOffset = TextLength();
	Insert(inText, inLength, inRuns);
	int32 endOffset = TextLength();

	AddHyperTextAction(startOffset, endOffset, action);
}


const HyperTextView::ActionInfo*
HyperTextView::_ActionInfoAt(const BPoint& where) const
{
	int32 offset = OffsetAt(where);

	ActionInfo pointer(offset, offset + 1, NULL);

	const ActionInfo* action = fActionInfos->BinarySearch(pointer,
			ActionInfo::CompareEqualIfIntersecting);
	return action;
}


HyperTextAction*
HyperTextView::_ActionAt(const BPoint& where) const
{
	const ActionInfo* action = _ActionInfoAt(where);

	if (action != NULL) {
		// verify that the text region was hit
		BRegion textRegion;
		GetTextRegion(action->startOffset, action->endOffset, &textRegion);
		if (textRegion.Contains(where))
			return action->action;
	}

	return NULL;
}