/*
 * Copyright 2003-2015 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Jérôme Duval,
 *		Axel Dörfler (axeld@pinc-software.de)
 *		Andrew McCall (mccall@digitalparadise.co.uk)
 *		Philippe Saint-Pierre stpere@gmail.com
 */
 
 
#include "MouseView.h"
 
#include <algorithm>
 
#include <Box.h>
#include <Button.h>
#include <Debug.h>
#include <GradientLinear.h>
#include <GradientRadial.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <Region.h>
#include <Shape.h>
#include <Slider.h>
#include <TextControl.h>
#include <TranslationUtils.h>
#include <TranslatorFormats.h>
#include <Window.h>
 
#include "MouseConstants.h"
#include "MouseSettings.h"
#include "MouseWindow.h"
 
 
static const int32 kButtonTop = 3;
static const int32 kMouseDownWidth = 72;
static const int32 kMouseDownHeight = 35;
 
static const int32 kOneButtonOffsets[4]
	= { 0, kMouseDownWidth };
static const int32 kTwoButtonOffsets[4]
	= { 0, kMouseDownWidth / 2, kMouseDownWidth };
static const int32 kThreeButtonOffsets[4]
	= { 0, kMouseDownWidth / 3 + 1, kMouseDownWidth / 3 * 2, kMouseDownWidth };
 
static const rgb_color kButtonTextColor = { 0, 0, 0, 255 };
static const rgb_color kMouseShadowColor = { 100, 100, 100, 128 };
static const rgb_color kMouseBodyTopColor = { 0xed, 0xed, 0xed, 255 };
static const rgb_color kMouseBodyBottomColor = { 0x85, 0x85, 0x85, 255 };
static const rgb_color kMouseOutlineColor = { 0x51, 0x51, 0x51, 255 };
static const rgb_color kMouseButtonOutlineColor = { 0xa0, 0xa0, 0xa0, 255 };
static const rgb_color kButtonPressedColor = { 110, 110, 110, 110 };
 
 
static const int32*
getButtonOffsets(int32 type)
{
	switch (type) {
		case 1:
			return kOneButtonOffsets;
		case 2:
			return kTwoButtonOffsets;
		case 3:
		default:
			return kThreeButtonOffsets;
	}
}
 
 
static uint32
getMappingNumber(int32 mapping)
{
	switch (mapping) {
		case B_PRIMARY_MOUSE_BUTTON:
			return 0;
		case B_SECONDARY_MOUSE_BUTTON:
			return 1;
		case B_TERTIARY_MOUSE_BUTTON:
			return 2;
	}
	return 0;
}
 
 
//	#pragma mark -
 
 
MouseView::MouseView(const MouseSettings &settings)
	:
	BView("mouse_view", B_PULSE_NEEDED | B_WILL_DRAW),
	fSettings(settings),
	fType(-1),
	fButtons(0),
	fOldButtons(0)
{
	SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
	fScaling = std::max(1.0f, be_plain_font->Size() / 12.0f);
}
 
 
MouseView::~MouseView()
{
}
 
 
void
MouseView::SetMouseType(int32 type)
{
	fType = type;
	Invalidate();
}
 
 
void
MouseView::MouseMapUpdated()
{
	Invalidate();
}
 
 
void
MouseView::UpdateFromSettings()
{
	SetMouseType(fSettings.MouseType());
}
 
 
void
MouseView::GetPreferredSize(float* _width, float* _height)
{
	if (_width != NULL)
		*_width = fScaling * (kMouseDownWidth + 2);
	if (_height != NULL)
		*_height = fScaling * 104;
}
 
 
void
MouseView::AttachedToWindow()
{
	AdoptParentColors();
 
	UpdateFromSettings();
	_CreateButtonsPicture();
 
	font_height fontHeight;
	GetFontHeight(&fontHeight);
	fDigitHeight = int32(ceilf(fontHeight.ascent) + ceilf(fontHeight.descent));
	fDigitBaseline = int32(ceilf(fontHeight.ascent));
}
 
 
void
MouseView::MouseUp(BPoint)
{
	fButtons = 0;
	Invalidate(_ButtonsRect());
	fOldButtons = fButtons;
}
 
 
void
MouseView::MouseDown(BPoint where)
{
	BMessage *mouseMsg = Window()->CurrentMessage();
	fButtons = mouseMsg->FindInt32("buttons");
	int32 modifiers = mouseMsg->FindInt32("modifiers");
	if (modifiers & B_CONTROL_KEY) {
		if (modifiers & B_COMMAND_KEY)
			fButtons = B_TERTIARY_MOUSE_BUTTON;
		else
			fButtons = B_SECONDARY_MOUSE_BUTTON;
	}
 
	// Get the current clipping region before requesting any updates.
	// Otherwise those parts would be excluded from the region.
	BRegion clipping;
	GetClippingRegion(&clipping);
 
	if (fOldButtons != fButtons) {
		Invalidate(_ButtonsRect());
		fOldButtons = fButtons;
	}
 
	const int32* offset = getButtonOffsets(fType);
	int32 button = -1;
	for (int32 i = 0; i <= fType; i++) {
		if (_ButtonRect(offset, i).Contains(where)) {
			button = i;
			break;
		}
	}
	if (button < 0)
		return;
 
	// We are setup to receive all mouse events, even if our window
	// is not active, so make sure that we don't display the menu when
	// the user clicked inside our view, but another window is on top.
	if (clipping.Contains(where)) {
		button = _ConvertFromVisualOrder(button);
 
		BPopUpMenu menu("Mouse Map Menu");
		BMessage message(kMsgMouseMap);
		message.AddInt32("button", button);
 
		menu.AddItem(new BMenuItem("1", new BMessage(message)));
		menu.AddItem(new BMenuItem("2", new BMessage(message)));
		menu.AddItem(new BMenuItem("3", new BMessage(message)));
 
		menu.ItemAt(getMappingNumber(fSettings.Mapping(button)))
			->SetMarked(true);
		menu.SetTargetForItems(Window());
 
		ConvertToScreen(&where);
		menu.Go(where, true);
	}
}
 
 
void
MouseView::Draw(BRect updateFrame)
{
	SetDrawingMode(B_OP_ALPHA);
	SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
	SetScale(fScaling * 1.8);
 
	BShape mouseShape;
	mouseShape.MoveTo(BPoint(16, 12));
	// left
	BPoint control[3] = { BPoint(12, 16), BPoint(8, 64), BPoint(32, 64) };
	mouseShape.BezierTo(control);
	// right
	BPoint control2[3] = { BPoint(56, 64), BPoint(52, 16), BPoint(48, 12) };
	mouseShape.BezierTo(control2);
	// top
	BPoint control3[3] = { BPoint(44, 8), BPoint(20, 8), BPoint(16, 12) };
	mouseShape.BezierTo(control3);
	mouseShape.Close();
 
	// Draw the shadow
	SetOrigin(-17 * fScaling, -11 * fScaling);
	SetHighColor(kMouseShadowColor);
	FillShape(&mouseShape, B_SOLID_HIGH);
 
	// Draw the body
	SetOrigin(-21 * fScaling, -14 * fScaling);
	BGradientRadial bodyGradient(28, 24, 128);
	bodyGradient.AddColor(kMouseBodyTopColor, 0);
	bodyGradient.AddColor(kMouseBodyBottomColor, 255);
 
	FillShape(&mouseShape, bodyGradient);
 
	// Draw the outline
	SetPenSize(1 / 1.8 / fScaling);
	SetDrawingMode(B_OP_OVER);
	SetHighColor(kMouseOutlineColor);
 
	StrokeShape(&mouseShape, B_SOLID_HIGH);
 
	// bottom button border
	BShape buttonsOutline;
	buttonsOutline.MoveTo(BPoint(13, 27));
	BPoint control4[] = { BPoint(18, 30), BPoint(46, 30), BPoint(51, 27) };
	buttonsOutline.BezierTo(control4);
 
	SetHighColor(kMouseButtonOutlineColor);
	StrokeShape(&buttonsOutline, B_SOLID_HIGH);
 
	SetScale(1);
	SetOrigin(0, 0);
 
	// Separator between the buttons
	const int32* offset = getButtonOffsets(fType);
	for (int32 i = 1; i < fType; i++) {
		BRect buttonRect = _ButtonRect(offset, i);
		StrokeLine(buttonRect.LeftTop(), buttonRect.LeftBottom());
	}
 
	mouse_map map;
	fSettings.Mapping(map);
 
	SetDrawingMode(B_OP_OVER);
 
	if (fButtons != 0)
		ClipToPicture(&fButtonsPicture, B_ORIGIN, false);
 
	for (int32 i = 0; i < fType; i++) {
		// draw mapping number centered over the button
 
		bool pressed = (fButtons & map.button[_ConvertFromVisualOrder(i)]) != 0;
			// is button currently pressed?
		if (pressed) {
			SetDrawingMode(B_OP_ALPHA);
			SetHighColor(kButtonPressedColor);
			FillRect(_ButtonRect(offset, i));
		}
 
		BRect border(fScaling * (offset[i] + 1), fScaling * (kButtonTop + 5),
			fScaling * offset[i + 1] - 1,
			fScaling * (kButtonTop + kMouseDownHeight - 4));
		if (i == 0)
			border.left += fScaling * 5;
		if (i == fType - 1)
			border.right -= fScaling * 4;
 
		char number[2] = {0};
		number[0] = getMappingNumber(map.button[_ConvertFromVisualOrder(i)])
			+ '1';
 
		SetDrawingMode(B_OP_OVER);
		SetHighColor(kButtonTextColor);
		DrawString(number, BPoint(
			border.left + (border.Width() - StringWidth(number)) / 2,
			border.top + fDigitBaseline
				+ (border.IntegerHeight() - fDigitHeight) / 2));
	}
 
	if (fButtons != 0)
		ClipToPicture(NULL);
}
 
 
BRect
MouseView::_ButtonsRect() const
{
	return BRect(0, fScaling * kButtonTop, fScaling * kMouseDownWidth,
			fScaling * (kButtonTop + kMouseDownHeight));
}
 
 
BRect
MouseView::_ButtonRect(const int32* offsets, int index) const
{
	return BRect(fScaling * offsets[index], fScaling * kButtonTop,
		fScaling * offsets[index + 1] - 1,
		fScaling * (kButtonTop + kMouseDownHeight));
}
 
 
int32
MouseView::_ConvertFromVisualOrder(int32 i)
{
	if (fType < 3)
		return i;
 
	switch (i) {
		case 0:
		default:
			return 0;
		case 1:
			return 2;
		case 2:
			return 1;
	}
}
 
 
void
MouseView::_CreateButtonsPicture()
{
	BeginPicture(&fButtonsPicture);
	SetScale(1.8 * fScaling);
	SetOrigin(-21 * fScaling, -14 * fScaling);
 
	BShape mouseShape;
	mouseShape.MoveTo(BPoint(48, 12));
	// top
	BPoint control3[3] = { BPoint(44, 8), BPoint(20, 8), BPoint(16, 12) };
	mouseShape.BezierTo(control3);
	// left
	BPoint control[3] = { BPoint(12, 16), BPoint(13, 27), BPoint(13, 27) };
	mouseShape.BezierTo(control);
	// bottom
	BPoint control4[] = { BPoint(18, 30), BPoint(46, 30), BPoint(51, 27) };
	mouseShape.BezierTo(control4);
	// right
	BPoint control2[3] = { BPoint(51, 27), BPoint(50, 14), BPoint(48, 12) };
	mouseShape.BezierTo(control2);
 
	mouseShape.Close();
 
	SetHighColor(255, 0, 0, 255);
	FillShape(&mouseShape, B_SOLID_HIGH);
 
	EndPicture();
	SetScale(1);
}

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