/*
 * Copyright 2008-2014 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		John Scipione, jscipione@gmail.com
 *		Clemens Zeidler, haiku@Clemens-Zeidler.de
 */
 
 
#include "TouchpadPrefView.h"
 
#include <stdio.h>
 
#include <Alert.h>
#include <Box.h>
#include <Catalog.h>
#include <CheckBox.h>
#include <ControlLook.h>
#include <File.h>
#include <FindDirectory.h>
#include <Input.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Message.h>
#include <Path.h>
#include <Screen.h>
#include <SpaceLayoutItem.h>
#include <Window.h>
 
#include <keyboard_mouse_driver.h>
 
 
const uint32 SCROLL_X_DRAG = 'sxdr';
const uint32 SCROLL_Y_DRAG = 'sydr';
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "TouchpadPrefView"
 
 
TouchpadView::TouchpadView(BRect frame)
	:
	BView(frame, "TouchpadView", B_FOLLOW_NONE, B_WILL_DRAW)
{
	fXTracking = false;
	fYTracking = false;
	fOffScreenView  = NULL;
	fOffScreenBitmap = NULL;
 
	fPrefRect = frame;
	fPadRect = fPrefRect;
	fPadRect.InsetBy(10, 10);
	fXScrollRange = fPadRect.Width();
	fYScrollRange = fPadRect.Height();
 
}
 
 
TouchpadView::~TouchpadView()
{
	delete fOffScreenBitmap;
}
 
 
void
TouchpadView::Draw(BRect updateRect)
{
	DrawSliders();
}
 
 
void
TouchpadView::MouseDown(BPoint point)
{
	if (fXScrollDragZone.Contains(point)) {
		fXTracking = true;
		fOldXScrollRange = fXScrollRange;
		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
	}
 
	if (fYScrollDragZone.Contains(point)) {
		fYTracking = true;
		fOldYScrollRange = fYScrollRange;
		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
	}
}
 
 
void
TouchpadView::MouseUp(BPoint point)
{
	if (!fXTracking && !fYTracking)
		return;
 
	fXTracking = false;
	fYTracking = false;
 
	const float kSoftScrollLimit = 0.7;
 
	int32 result = 0;
	if (GetRightScrollRatio() > kSoftScrollLimit
		|| GetBottomScrollRatio() > kSoftScrollLimit) {
		BAlert* alert = new BAlert(B_TRANSLATE("Please confirm"),
			B_TRANSLATE("The new scroll area is very large and may impede "
				"normal mouse operation. Do you really want to change it?"),
			B_TRANSLATE("OK"), B_TRANSLATE("Cancel"),
			NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
		alert->SetShortcut(1, B_ESCAPE);
		result = alert->Go();
	}
 
	if (result == 0) {
		BMessage msg(SCROLL_AREA_CHANGED);
		Invoke(&msg);
	} else {
		if (GetRightScrollRatio() > kSoftScrollLimit)
			fXScrollRange = fOldXScrollRange;
		if (GetBottomScrollRatio() > kSoftScrollLimit)
			fYScrollRange = fOldYScrollRange;
		DrawSliders();
	}
}
 
 
void
TouchpadView::AttachedToWindow()
{
	if (!fOffScreenView)
		fOffScreenView = new BView(Bounds(), "", B_FOLLOW_ALL, B_WILL_DRAW);
 
	if (!fOffScreenBitmap) {
		fOffScreenBitmap = new BBitmap(Bounds(), B_CMAP8, true, false);
 
		if (fOffScreenBitmap && fOffScreenView)
			fOffScreenBitmap->AddChild(fOffScreenView);
	}
}
 
 
void
TouchpadView::SetValues(float rightRange, float bottomRange)
{
	fXScrollRange = fPadRect.Width() * (1 - rightRange);
	fYScrollRange = fPadRect.Height() * (1 - bottomRange);
	Invalidate();
}
 
 
void
TouchpadView::GetPreferredSize(float* width, float* height)
{
	if (width != NULL)
		*width = fPrefRect.Width();
	if (height != NULL)
		*height =  fPrefRect.Height();
}
 
 
void
TouchpadView::MouseMoved(BPoint point, uint32 transit, const BMessage* message)
{
	if (fXTracking) {
		if (point.x > fPadRect.right)
			fXScrollRange = fPadRect.Width();
		else if (point.x < fPadRect.left)
			fXScrollRange = 0;
		else
			fXScrollRange = point.x - fPadRect.left;
 
		DrawSliders();
	}
 
	if (fYTracking) {
		if (point.y > fPadRect.bottom)
			fYScrollRange = fPadRect.Height();
		else if (point.y < fPadRect.top)
			fYScrollRange = 0;
		else
			fYScrollRange = point.y - fPadRect.top;
 
		DrawSliders();
	}
}
 
 
 
void
TouchpadView::DrawSliders()
{
	BView* view = fOffScreenView != NULL ? fOffScreenView : this;
 
	if (!LockLooper())
		return;
 
	if (fOffScreenBitmap->Lock()) {
		view->SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
		view->FillRect(Bounds());
		view->SetHighColor(100, 100, 100);
		view->FillRoundRect(fPadRect, 4, 4);
 
		int32 dragSize = 3; // half drag size
 
		// scroll areas
		view->SetHighColor(145, 100, 100);
		BRect rightRect(fPadRect.left + fXScrollRange,  fPadRect.top,
			fPadRect.right, fPadRect.bottom);
		view->FillRoundRect(rightRect, 4, 4);
 
		BRect bottomRect(fPadRect.left, fPadRect.top + fYScrollRange,
			fPadRect.right, fPadRect.bottom);
		view->FillRoundRect(bottomRect, 4, 4);
 
		// Stroke Rect
		view->SetHighColor(100, 100, 100);
		view->SetPenSize(2);
		view->StrokeRoundRect(fPadRect, 4, 4);
 
		// x scroll range line
		view->SetHighColor(200, 0, 0);
		view->StrokeLine(BPoint(fPadRect.left + fXScrollRange, fPadRect.top),
			BPoint(fPadRect.left + fXScrollRange, fPadRect.bottom));
 
		fXScrollDragZone = BRect(fPadRect.left + fXScrollRange - dragSize,
			fPadRect.top - dragSize, fPadRect.left + fXScrollRange + dragSize,
			fPadRect.bottom + dragSize);
		fXScrollDragZone1 = BRect(fPadRect.left + fXScrollRange - dragSize,
			fPadRect.top - dragSize, fPadRect.left + fXScrollRange + dragSize,
			fPadRect.top + dragSize);
		view->FillRect(fXScrollDragZone1);
		fXScrollDragZone2 = BRect(fPadRect.left + fXScrollRange - dragSize,
			fPadRect.bottom - dragSize,
			fPadRect.left + fXScrollRange + dragSize,
			fPadRect.bottom + dragSize);
		view->FillRect(fXScrollDragZone2);
 
		// y scroll range line
		view->StrokeLine(BPoint(fPadRect.left, fPadRect.top + fYScrollRange),
			BPoint(fPadRect.right, fPadRect.top  + fYScrollRange));
 
		fYScrollDragZone = BRect(fPadRect.left - dragSize,
			fPadRect.top + fYScrollRange - dragSize,
			fPadRect.right  + dragSize,
			fPadRect.top + fYScrollRange + dragSize);
		fYScrollDragZone1 = BRect(fPadRect.left - dragSize,
			fPadRect.top + fYScrollRange - dragSize, fPadRect.left  + dragSize,
			fPadRect.top + fYScrollRange + dragSize);
		view->FillRect(fYScrollDragZone1);
		fYScrollDragZone2 = BRect(fPadRect.right - dragSize,
			fPadRect.top + fYScrollRange - dragSize, fPadRect.right  + dragSize,
			fPadRect.top + fYScrollRange + dragSize);
		view->FillRect(fYScrollDragZone2);
 
		view->Sync();
		fOffScreenBitmap->Unlock();
		DrawBitmap(fOffScreenBitmap, B_ORIGIN);
	}
 
	UnlockLooper();
}
 
 
//	#pragma mark - TouchpadPrefView
 
 
TouchpadPrefView::TouchpadPrefView()
	:
	BGroupView()
{
	SetupView();
	// set view values
	SetValues(&fTouchpadPref.Settings());
	if (fTouchpadPref.IsTouchpadConnected() == false) {
		DisablePref();
		fShowWarning->SetText(B_TRANSLATE("No touchpad found, the settings "
			"will have no effect."));
	}
	else
		fShowWarning->Hide();
}
 
 
TouchpadPrefView::~TouchpadPrefView()
{
}
 
 
void
TouchpadPrefView::MessageReceived(BMessage* message)
{
	touchpad_settings& settings = fTouchpadPref.Settings();
 
	switch (message->what) {
		case SCROLL_AREA_CHANGED:
			settings.scroll_rightrange = fTouchpadView->GetRightScrollRatio();
			settings.scroll_bottomrange = fTouchpadView->GetBottomScrollRatio();
			fRevertButton->SetEnabled(true);
			fTouchpadPref.UpdateSettings();
			break;
 
		case SCROLL_CONTROL_CHANGED:
			settings.scroll_twofinger = fTwoFingerBox->Value() == B_CONTROL_ON;
			settings.scroll_twofinger_horizontal
				= fTwoFingerHorizontalBox->Value() == B_CONTROL_ON;
			settings.scroll_acceleration = fScrollAccelSlider->Value();
			settings.scroll_xstepsize = (20 - fScrollStepXSlider->Value()) * 3;
			settings.scroll_ystepsize = (20 - fScrollStepYSlider->Value()) * 3;
			fRevertButton->SetEnabled(true);
			fTwoFingerHorizontalBox->SetEnabled(settings.scroll_twofinger);
			fTouchpadPref.UpdateSettings();
			break;
 
		case TAP_CONTROL_CHANGED:
			settings.tapgesture_sensibility = fTapSlider->Value();
			fRevertButton->SetEnabled(true);
			fTouchpadPref.UpdateSettings();
			break;
 
		case DEFAULT_SETTINGS:
			fTouchpadPref.Defaults();
			fRevertButton->SetEnabled(true);
			fTouchpadPref.UpdateSettings();
			SetValues(&settings);
			break;
 
		case REVERT_SETTINGS:
			fTouchpadPref.Revert();
			fTouchpadPref.UpdateSettings();
			fRevertButton->SetEnabled(false);
			SetValues(&settings);
			break;
 
		default:
			BView::MessageReceived(message);
	}
}
 
 
void
TouchpadPrefView::AttachedToWindow()
{
	fTouchpadView->SetTarget(this);
	fTwoFingerBox->SetTarget(this);
	fTwoFingerHorizontalBox->SetTarget(this);
	fScrollStepXSlider->SetTarget(this);
	fScrollStepYSlider->SetTarget(this);
	fScrollAccelSlider->SetTarget(this);
	fTapSlider->SetTarget(this);
	fDefaultButton->SetTarget(this);
	fRevertButton->SetTarget(this);
	BSize size = PreferredSize();
	Window()->ResizeTo(size.width, size.height);
 
	BPoint position = fTouchpadPref.WindowPosition();
	// center window on screen if it had a bad position
	if (position.x < 0 && position.y < 0)
		Window()->CenterOnScreen();
	else
		Window()->MoveTo(position);
}
 
 
void
TouchpadPrefView::DetachedFromWindow()
{
	fTouchpadPref.SetWindowPosition(Window()->Frame().LeftTop());
}
 
 
void
TouchpadPrefView::SetupView()
{
	SetLayout(new BGroupLayout(B_VERTICAL));
	BBox* scrollBox = new BBox("Touchpad");
	scrollBox->SetLabel(B_TRANSLATE("Scrolling"));
 
	fTouchpadView = new TouchpadView(BRect(0, 0, 130, 120));
	fTouchpadView->SetExplicitMaxSize(BSize(130, 120));
 
	// Create the "Mouse Speed" slider...
	fScrollAccelSlider = new BSlider("scroll_accel",
		B_TRANSLATE("Acceleration"),
		new BMessage(SCROLL_CONTROL_CHANGED), 0, 20, B_HORIZONTAL);
	fScrollAccelSlider->SetHashMarks(B_HASH_MARKS_BOTTOM);
	fScrollAccelSlider->SetHashMarkCount(7);
	fScrollAccelSlider->SetLimitLabels(B_TRANSLATE("Slow"),
		B_TRANSLATE("Fast"));
	fScrollAccelSlider->SetExplicitMinSize(BSize(150, B_SIZE_UNSET));
 
	fScrollStepXSlider = new BSlider("scroll_stepX",
		B_TRANSLATE("Horizontal"),
		new BMessage(SCROLL_CONTROL_CHANGED),
		0, 20, B_HORIZONTAL);
	fScrollStepXSlider->SetHashMarks(B_HASH_MARKS_BOTTOM);
	fScrollStepXSlider->SetHashMarkCount(7);
	fScrollStepXSlider->SetLimitLabels(B_TRANSLATE("Slow"),
		B_TRANSLATE("Fast"));
 
	fScrollStepYSlider = new BSlider("scroll_stepY",
		B_TRANSLATE("Vertical"),
		new BMessage(SCROLL_CONTROL_CHANGED), 0, 20, B_HORIZONTAL);
	fScrollStepYSlider->SetHashMarks(B_HASH_MARKS_BOTTOM);
	fScrollStepYSlider->SetHashMarkCount(7);
	fScrollStepYSlider->SetLimitLabels(B_TRANSLATE("Slow"),
		B_TRANSLATE("Fast"));
 
	fTwoFingerBox = new BCheckBox(B_TRANSLATE("Two finger scrolling"),
		new BMessage(SCROLL_CONTROL_CHANGED));
	fTwoFingerHorizontalBox = new BCheckBox(
		B_TRANSLATE("Horizontal scrolling"),
		new BMessage(SCROLL_CONTROL_CHANGED));
 
	float spacing = be_control_look->DefaultItemSpacing();
 
	BView* scrollPrefLeftLayout
		= BLayoutBuilder::Group<>(B_VERTICAL, 0)
		.Add(fTouchpadView)
		.AddStrut(spacing)
		.Add(fTwoFingerBox)
		.AddGroup(B_HORIZONTAL, 0)
			.AddStrut(spacing * 2)
			.Add(fTwoFingerHorizontalBox)
			.End()
		.AddGlue()
		.View();
 
	BGroupView* scrollPrefRightLayout = new BGroupView(B_VERTICAL);
	scrollPrefRightLayout->AddChild(fScrollAccelSlider);
	scrollPrefRightLayout->AddChild(fScrollStepXSlider);
	scrollPrefRightLayout->AddChild(fScrollStepYSlider);
 
	BGroupLayout* scrollPrefLayout = new BGroupLayout(B_HORIZONTAL);
	scrollPrefLayout->SetSpacing(spacing);
	scrollPrefLayout->SetInsets(spacing,
		scrollBox->TopBorderOffset() * 2 + spacing, spacing, spacing);
	scrollBox->SetLayout(scrollPrefLayout);
 
	scrollPrefLayout->AddView(scrollPrefLeftLayout);
	scrollPrefLayout->AddItem(BSpaceLayoutItem::CreateVerticalStrut(spacing
		* 1.5));
	scrollPrefLayout->AddView(scrollPrefRightLayout);
 
	BBox* tapBox = new BBox("tapbox");
	tapBox->SetLabel(B_TRANSLATE("Tapping"));
 
	BGroupLayout* tapPrefLayout = new BGroupLayout(B_HORIZONTAL);
	tapPrefLayout->SetInsets(spacing, tapBox->TopBorderOffset() * 2 + spacing,
		spacing, spacing);
	tapBox->SetLayout(tapPrefLayout);
 
	fTapSlider = new BSlider("tap_sens", B_TRANSLATE("Sensitivity"),
		new BMessage(TAP_CONTROL_CHANGED), 0, spacing * 2, B_HORIZONTAL);
	fTapSlider->SetHashMarks(B_HASH_MARKS_BOTTOM);
	fTapSlider->SetHashMarkCount(7);
	fTapSlider->SetLimitLabels(B_TRANSLATE("Off"), B_TRANSLATE("High"));
 
	tapPrefLayout->AddView(fTapSlider);
 
	fDefaultButton = new BButton(B_TRANSLATE("Defaults"),
		new BMessage(DEFAULT_SETTINGS));
 
	fRevertButton = new BButton(B_TRANSLATE("Revert"),
		new BMessage(REVERT_SETTINGS));
	fRevertButton->SetEnabled(false);
 
	fShowWarning = new BStringView("warning", "");
 
	BLayoutBuilder::Group<>(this, B_VERTICAL)
		.SetInsets(B_USE_WINDOW_SPACING)
		.Add(fShowWarning)
		.Add(scrollBox)
		.Add(tapBox)
		.AddGroup(B_HORIZONTAL)
			.Add(fDefaultButton)
			.Add(fRevertButton)
			.AddGlue()
			.End()
		.End();
}
 
 
void
TouchpadPrefView::SetValues(touchpad_settings* settings)
{
	fTouchpadView->SetValues(settings->scroll_rightrange,
		settings->scroll_bottomrange);
	fTwoFingerBox->SetValue(settings->scroll_twofinger
		? B_CONTROL_ON : B_CONTROL_OFF);
	fTwoFingerHorizontalBox->SetValue(settings->scroll_twofinger_horizontal
		? B_CONTROL_ON : B_CONTROL_OFF);
	fTwoFingerHorizontalBox->SetEnabled(settings->scroll_twofinger);
	fScrollStepXSlider->SetValue(20 - settings->scroll_xstepsize / 2);
	fScrollStepYSlider->SetValue(20 - settings->scroll_ystepsize / 2);
	fScrollAccelSlider->SetValue(settings->scroll_acceleration);
	fTapSlider->SetValue(settings->tapgesture_sensibility);
}
 
 
void
TouchpadPrefView::DisablePref()
{
	fTwoFingerBox->SetEnabled(false);
	fTwoFingerHorizontalBox->SetEnabled(false);
	fRevertButton->SetEnabled(false);
	fDefaultButton->SetEnabled(false);
	fTapSlider->SetEnabled(false);
	fScrollAccelSlider->SetEnabled(false);
	fScrollStepXSlider->SetEnabled(false);
	fScrollStepYSlider->SetEnabled(false);
}

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

V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.