1/*
2 * Copyright 2019, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Author:
6 *		Preetpal Kaur <preetpalok123@gmail.com>
7 */
8
9
10#include "InputTouchpadPrefView.h"
11
12#include <stdio.h>
13
14#include <Alert.h>
15#include <Box.h>
16#include <Catalog.h>
17#include <CheckBox.h>
18#include <ControlLook.h>
19#include <File.h>
20#include <FindDirectory.h>
21#include <Input.h>
22#include <LayoutBuilder.h>
23#include <Locale.h>
24#include <MenuField.h>
25#include <MenuItem.h>
26#include <Message.h>
27#include <Path.h>
28#include <Screen.h>
29#include <SeparatorView.h>
30#include <SpaceLayoutItem.h>
31#include <Window.h>
32
33#include <keyboard_mouse_driver.h>
34
35
36const uint32 SCROLL_X_DRAG = 'sxdr';
37const uint32 SCROLL_Y_DRAG = 'sydr';
38
39#undef B_TRANSLATION_CONTEXT
40#define B_TRANSLATION_CONTEXT "TouchpadPrefView"
41
42
43TouchpadView::TouchpadView(BRect frame)
44	:
45	BView(frame, "TouchpadView", B_FOLLOW_NONE, B_WILL_DRAW)
46{
47	fXTracking = false;
48	fYTracking = false;
49	fOffScreenView = NULL;
50	fOffScreenBitmap = NULL;
51
52	fPrefRect = frame;
53	fPadRect = fPrefRect;
54	fPadRect.InsetBy(10, 10);
55	fXScrollRange = fPadRect.Width();
56	fYScrollRange = fPadRect.Height();
57}
58
59
60TouchpadView::~TouchpadView()
61{
62	delete fOffScreenBitmap;
63}
64
65
66void
67TouchpadView::Draw(BRect updateRect)
68{
69	DrawSliders();
70}
71
72
73void
74TouchpadView::MouseDown(BPoint point)
75{
76	if (fXScrollDragZone.Contains(point)) {
77		fXTracking = true;
78		fOldXScrollRange = fXScrollRange;
79		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
80	}
81
82	if (fYScrollDragZone.Contains(point)) {
83		fYTracking = true;
84		fOldYScrollRange = fYScrollRange;
85		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
86	}
87}
88
89
90void
91TouchpadView::MouseUp(BPoint point)
92{
93	if (!fXTracking && !fYTracking)
94		return;
95
96	fXTracking = false;
97	fYTracking = false;
98
99	const float kSoftScrollLimit = 0.7;
100
101	int32 result = 0;
102	if (GetRightScrollRatio() > kSoftScrollLimit
103		|| GetBottomScrollRatio() > kSoftScrollLimit) {
104		BAlert* alert = new BAlert(B_TRANSLATE("Please confirm"),
105			B_TRANSLATE(
106				"The new scroll area is very large and may impede "
107				"normal mouse operation. Do you really want to change it?"),
108			B_TRANSLATE("OK"), B_TRANSLATE("Cancel"), NULL, B_WIDTH_AS_USUAL,
109			B_WARNING_ALERT);
110		alert->SetShortcut(1, B_ESCAPE);
111		result = alert->Go();
112	}
113
114	if (result == 0) {
115		BMessage msg(SCROLL_AREA_CHANGED);
116		Invoke(&msg);
117	} else {
118		if (GetRightScrollRatio() > kSoftScrollLimit)
119			fXScrollRange = fOldXScrollRange;
120		if (GetBottomScrollRatio() > kSoftScrollLimit)
121			fYScrollRange = fOldYScrollRange;
122		DrawSliders();
123	}
124}
125
126
127void
128TouchpadView::AttachedToWindow()
129{
130	if (!fOffScreenView)
131		fOffScreenView = new BView(Bounds(), "", B_FOLLOW_ALL, B_WILL_DRAW);
132
133	if (!fOffScreenBitmap) {
134		fOffScreenBitmap = new BBitmap(Bounds(), B_CMAP8, true, false);
135
136		if (fOffScreenBitmap && fOffScreenView)
137			fOffScreenBitmap->AddChild(fOffScreenView);
138	}
139}
140
141
142void
143TouchpadView::SetValues(float rightRange, float bottomRange)
144{
145	fXScrollRange = fPadRect.Width() * (1 - rightRange);
146	fYScrollRange = fPadRect.Height() * (1 - bottomRange);
147	Invalidate();
148}
149
150
151void
152TouchpadView::GetPreferredSize(float* width, float* height)
153{
154	if (width != NULL)
155		*width = fPrefRect.Width();
156	if (height != NULL)
157		*height = fPrefRect.Height();
158}
159
160
161void
162TouchpadView::MouseMoved(BPoint point, uint32 transit, const BMessage* message)
163{
164	if (fXTracking) {
165		if (point.x > fPadRect.right)
166			fXScrollRange = fPadRect.Width();
167		else if (point.x < fPadRect.left)
168			fXScrollRange = 0;
169		else
170			fXScrollRange = point.x - fPadRect.left;
171
172		DrawSliders();
173	}
174
175	if (fYTracking) {
176		if (point.y > fPadRect.bottom)
177			fYScrollRange = fPadRect.Height();
178		else if (point.y < fPadRect.top)
179			fYScrollRange = 0;
180		else
181			fYScrollRange = point.y - fPadRect.top;
182
183		DrawSliders();
184	}
185}
186
187
188void
189TouchpadView::DrawSliders()
190{
191	BView* view = fOffScreenView != NULL ? fOffScreenView : this;
192
193	if (!LockLooper())
194		return;
195
196	if (fOffScreenBitmap->Lock()) {
197		view->SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
198		view->FillRect(Bounds());
199		view->SetHighColor(100, 100, 100);
200		view->FillRoundRect(fPadRect, 4, 4);
201
202		int32 dragSize = 3; // half drag size
203
204		// scroll areas
205		view->SetHighColor(145, 100, 100);
206		BRect rightRect(fPadRect.left + fXScrollRange, fPadRect.top,
207			fPadRect.right, fPadRect.bottom);
208		view->FillRoundRect(rightRect, 4, 4);
209
210		BRect bottomRect(fPadRect.left, fPadRect.top + fYScrollRange,
211			fPadRect.right, fPadRect.bottom);
212		view->FillRoundRect(bottomRect, 4, 4);
213
214		// Stroke Rect
215		view->SetHighColor(100, 100, 100);
216		view->SetPenSize(2);
217		view->StrokeRoundRect(fPadRect, 4, 4);
218
219		// x scroll range line
220		view->SetHighColor(200, 0, 0);
221		view->StrokeLine(BPoint(fPadRect.left + fXScrollRange, fPadRect.top),
222			BPoint(fPadRect.left + fXScrollRange, fPadRect.bottom));
223
224		fXScrollDragZone = BRect(fPadRect.left + fXScrollRange - dragSize,
225			fPadRect.top - dragSize, fPadRect.left + fXScrollRange + dragSize,
226			fPadRect.bottom + dragSize);
227		BRect xscrollDragZone1 = BRect(fPadRect.left + fXScrollRange - dragSize,
228			fPadRect.top - dragSize, fPadRect.left + fXScrollRange + dragSize,
229			fPadRect.top + dragSize);
230		view->FillRect(xscrollDragZone1);
231		BRect xscrollDragZone2 = BRect(fPadRect.left + fXScrollRange - dragSize,
232			fPadRect.bottom - dragSize,
233			fPadRect.left + fXScrollRange + dragSize,
234			fPadRect.bottom + dragSize);
235		view->FillRect(xscrollDragZone2);
236
237		// y scroll range line
238		view->StrokeLine(BPoint(fPadRect.left, fPadRect.top + fYScrollRange),
239			BPoint(fPadRect.right, fPadRect.top + fYScrollRange));
240
241		fYScrollDragZone = BRect(fPadRect.left - dragSize,
242			fPadRect.top + fYScrollRange - dragSize, fPadRect.right + dragSize,
243			fPadRect.top + fYScrollRange + dragSize);
244		BRect yscrollDragZone1 = BRect(fPadRect.left - dragSize,
245			fPadRect.top + fYScrollRange - dragSize, fPadRect.left + dragSize,
246			fPadRect.top + fYScrollRange + dragSize);
247		view->FillRect(yscrollDragZone1);
248		BRect yscrollDragZone2 = BRect(fPadRect.right - dragSize,
249			fPadRect.top + fYScrollRange - dragSize, fPadRect.right + dragSize,
250			fPadRect.top + fYScrollRange + dragSize);
251		view->FillRect(yscrollDragZone2);
252
253		view->Sync();
254		fOffScreenBitmap->Unlock();
255		DrawBitmap(fOffScreenBitmap, B_ORIGIN);
256	}
257
258	UnlockLooper();
259}
260
261
262//	#pragma mark - TouchpadPrefView
263
264
265TouchpadPrefView::TouchpadPrefView(BInputDevice* dev)
266	:
267	BGroupView(),
268	fTouchpadPref(dev)
269{
270	SetupView();
271	// set view values
272	SetValues(&fTouchpadPref.Settings());
273}
274
275
276TouchpadPrefView::~TouchpadPrefView()
277{
278}
279
280
281void
282TouchpadPrefView::MessageReceived(BMessage* message)
283{
284	touchpad_settings& settings = fTouchpadPref.Settings();
285
286	switch (message->what) {
287		case SCROLL_AREA_CHANGED:
288			settings.scroll_rightrange = fTouchpadView->GetRightScrollRatio();
289			settings.scroll_bottomrange = fTouchpadView->GetBottomScrollRatio();
290			fTouchpadPref.UpdateSettings();
291			break;
292
293		case SCROLL_CONTROL_CHANGED:
294			settings.scroll_twofinger = fTwoFingerBox->Value() == B_CONTROL_ON;
295			settings.scroll_twofinger_horizontal
296				= fTwoFingerHorizontalBox->Value() == B_CONTROL_ON;
297			settings.scroll_acceleration = fScrollAccelSlider->Value();
298			settings.scroll_xstepsize = (20 - fScrollStepXSlider->Value()) * 3;
299			settings.scroll_ystepsize = (20 - fScrollStepYSlider->Value()) * 3;
300			fTwoFingerHorizontalBox->SetEnabled(settings.scroll_twofinger);
301			fTouchpadPref.UpdateSettings();
302			break;
303
304		case TAP_CONTROL_CHANGED:
305			settings.tapgesture_sensibility = fTapSlider->Value();
306			fTouchpadPref.UpdateSettings();
307			break;
308
309		case PADBLOCK_TIME_CHANGED:
310			settings.padblocker_threshold = fPadBlockerSlider->Value();
311			// The maximum value means "disabled", but in the settings file that
312			// must be stored as 0
313			if (settings.padblocker_threshold == 1000)
314				settings.padblocker_threshold = 0;
315			fRevertButton->SetEnabled(true);
316			fTouchpadPref.UpdateSettings();
317			break;
318
319		case DEFAULT_SETTINGS:
320			fTouchpadPref.Defaults();
321			fRevertButton->SetEnabled(true);
322			fTouchpadPref.UpdateSettings();
323			SetValues(&settings);
324			break;
325
326		case REVERT_SETTINGS:
327			fTouchpadPref.Revert();
328			fTouchpadPref.UpdateSettings();
329			fRevertButton->SetEnabled(false);
330			SetValues(&settings);
331			break;
332
333		default:
334			BView::MessageReceived(message);
335	}
336}
337
338
339void
340TouchpadPrefView::AttachedToWindow()
341{
342	fTouchpadView->SetTarget(this);
343	fTwoFingerBox->SetTarget(this);
344	fTwoFingerHorizontalBox->SetTarget(this);
345	fScrollStepXSlider->SetTarget(this);
346	fScrollStepYSlider->SetTarget(this);
347	fScrollAccelSlider->SetTarget(this);
348	fPadBlockerSlider->SetTarget(this);
349	fTapSlider->SetTarget(this);
350	fDefaultButton->SetTarget(this);
351	fRevertButton->SetTarget(this);
352	BSize size = PreferredSize();
353	Window()->ResizeTo(size.width, size.height);
354
355	BPoint position = fTouchpadPref.WindowPosition();
356	// center window on screen if it had a bad position
357	if (position.x < 0 && position.y < 0)
358		Window()->CenterOnScreen();
359	else
360		Window()->MoveTo(position);
361}
362
363
364void
365TouchpadPrefView::DetachedFromWindow()
366{
367	fTouchpadPref.SetWindowPosition(Window()->Frame().LeftTop());
368}
369
370
371void
372TouchpadPrefView::SetupView()
373{
374	SetLayout(new BGroupLayout(B_VERTICAL));
375	BBox* scrollBox = new BBox("Touchpad");
376	scrollBox->SetLabel(B_TRANSLATE("Scrolling"));
377
378
379	fTouchpadView = new TouchpadView(BRect(0, 0, 130, 120));
380	fTouchpadView->SetExplicitMaxSize(BSize(130, 120));
381
382	// Create the "Mouse Speed" slider...
383	fScrollAccelSlider = new BSlider("scroll_accel",
384		B_TRANSLATE("Acceleration"),
385		new BMessage(SCROLL_CONTROL_CHANGED), 0, 20, B_HORIZONTAL);
386	fScrollAccelSlider->SetHashMarks(B_HASH_MARKS_BOTTOM);
387	fScrollAccelSlider->SetHashMarkCount(7);
388	fScrollAccelSlider->SetLimitLabels(
389		B_TRANSLATE("Slow"), B_TRANSLATE("Fast"));
390	fScrollAccelSlider->SetExplicitMinSize(BSize(150, B_SIZE_UNSET));
391
392	fScrollStepXSlider = new BSlider("scroll_stepX", B_TRANSLATE("Horizontal"),
393		new BMessage(SCROLL_CONTROL_CHANGED), 0, 20, B_HORIZONTAL);
394	fScrollStepXSlider->SetHashMarks(B_HASH_MARKS_BOTTOM);
395	fScrollStepXSlider->SetHashMarkCount(7);
396	fScrollStepXSlider->SetLimitLabels(
397		B_TRANSLATE("Slow"), B_TRANSLATE("Fast"));
398
399	fScrollStepYSlider = new BSlider("scroll_stepY", B_TRANSLATE("Vertical"),
400		new BMessage(SCROLL_CONTROL_CHANGED), 0, 20, B_HORIZONTAL);
401	fScrollStepYSlider->SetHashMarks(B_HASH_MARKS_BOTTOM);
402	fScrollStepYSlider->SetHashMarkCount(7);
403	fScrollStepYSlider->SetLimitLabels(
404		B_TRANSLATE("Slow"), B_TRANSLATE("Fast"));
405
406	fPadBlockerSlider
407		= new BSlider("padblocker", B_TRANSLATE("Keyboard lock delay"),
408			new BMessage(PADBLOCK_TIME_CHANGED), 5, 1000, B_HORIZONTAL);
409	fPadBlockerSlider->SetHashMarks(B_HASH_MARKS_BOTTOM);
410	fPadBlockerSlider->SetHashMarkCount(10);
411	fPadBlockerSlider->SetLimitLabels(
412		B_TRANSLATE("Quick"), B_TRANSLATE("Never"));
413
414	fTwoFingerBox = new BCheckBox(B_TRANSLATE("Two finger scrolling"),
415		new BMessage(SCROLL_CONTROL_CHANGED));
416	fTwoFingerHorizontalBox = new BCheckBox(B_TRANSLATE("Horizontal scrolling"),
417		new BMessage(SCROLL_CONTROL_CHANGED));
418
419	float spacing = be_control_look->DefaultItemSpacing();
420
421	BView* scrollPrefLeftLayout
422		= BLayoutBuilder::Group<>(B_VERTICAL, 0)
423		.Add(fTouchpadView)
424		.AddStrut(spacing)
425		.Add(fTwoFingerBox)
426		.AddGroup(B_HORIZONTAL, 0)
427			.AddStrut(spacing * 2)
428			.Add(fTwoFingerHorizontalBox)
429			.End()
430		.AddGlue()
431		.View();
432
433	BGroupView* scrollPrefRightLayout = new BGroupView(B_VERTICAL);
434	scrollPrefRightLayout->AddChild(fScrollAccelSlider);
435	scrollPrefRightLayout->AddChild(fScrollStepXSlider);
436	scrollPrefRightLayout->AddChild(fScrollStepYSlider);
437
438	BGroupLayout* scrollPrefLayout = new BGroupLayout(B_HORIZONTAL);
439	scrollPrefLayout->SetSpacing(spacing);
440	scrollPrefLayout->SetInsets(
441		spacing, scrollBox->TopBorderOffset() * 2 + spacing, spacing, spacing);
442	scrollBox->SetLayout(scrollPrefLayout);
443
444	scrollPrefLayout->AddView(scrollPrefLeftLayout);
445	scrollPrefLayout->AddItem(
446		BSpaceLayoutItem::CreateVerticalStrut(spacing * 1.5));
447	scrollPrefLayout->AddView(scrollPrefRightLayout);
448
449	fTapSlider = new BSlider("tap_sens", B_TRANSLATE("Tapping sensitivity"),
450		new BMessage(TAP_CONTROL_CHANGED), 0, spacing * 2, B_HORIZONTAL);
451	fTapSlider->SetHashMarks(B_HASH_MARKS_BOTTOM);
452	fTapSlider->SetHashMarkCount(7);
453	fTapSlider->SetLimitLabels(B_TRANSLATE("Off"), B_TRANSLATE("High"));
454
455	fDefaultButton
456		= new BButton(B_TRANSLATE("Defaults"), new BMessage(DEFAULT_SETTINGS));
457
458	fRevertButton
459		= new BButton(B_TRANSLATE("Revert"), new BMessage(REVERT_SETTINGS));
460	fRevertButton->SetEnabled(false);
461
462
463	BLayoutBuilder::Group<>(this, B_VERTICAL)
464		.SetInsets(B_USE_WINDOW_SPACING)
465		.Add(scrollBox)
466		.Add(fTapSlider)
467		.Add(fPadBlockerSlider)
468		.Add(new BSeparatorView(B_HORIZONTAL))
469			.AddGroup(B_HORIZONTAL)
470			.Add(fDefaultButton)
471			.Add(fRevertButton)
472			.AddGlue()
473		.End()
474	.End();
475}
476
477
478void
479TouchpadPrefView::SetValues(touchpad_settings* settings)
480{
481	fTouchpadView->SetValues(
482		settings->scroll_rightrange, settings->scroll_bottomrange);
483	fTwoFingerBox->SetValue(
484		settings->scroll_twofinger ? B_CONTROL_ON : B_CONTROL_OFF);
485	fTwoFingerHorizontalBox->SetValue(
486		settings->scroll_twofinger_horizontal ? B_CONTROL_ON : B_CONTROL_OFF);
487	fTwoFingerHorizontalBox->SetEnabled(settings->scroll_twofinger);
488	fScrollStepXSlider->SetValue(20 - settings->scroll_xstepsize / 2);
489	fScrollStepYSlider->SetValue(20 - settings->scroll_ystepsize / 2);
490	fScrollAccelSlider->SetValue(settings->scroll_acceleration);
491	fTapSlider->SetValue(settings->tapgesture_sensibility);
492	fPadBlockerSlider->SetValue(settings->padblocker_threshold);
493}
494