1/*
2 * Copyright 2003-2015 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Jérôme Duval,
7 *		Axel Dörfler (axeld@pinc-software.de)
8 *		Andrew McCall (mccall@digitalparadise.co.uk)
9 *		Philippe Saint-Pierre stpere@gmail.com
10 */
11
12
13#include "MouseView.h"
14
15#include <algorithm>
16
17#include <Box.h>
18#include <Button.h>
19#include <Debug.h>
20#include <GradientLinear.h>
21#include <GradientRadial.h>
22#include <MenuField.h>
23#include <MenuItem.h>
24#include <PopUpMenu.h>
25#include <Region.h>
26#include <Shape.h>
27#include <Slider.h>
28#include <TextControl.h>
29#include <TranslationUtils.h>
30#include <TranslatorFormats.h>
31#include <Window.h>
32
33#include "MouseConstants.h"
34#include "MouseSettings.h"
35#include "MouseWindow.h"
36
37
38static const int32 kButtonTop = 3;
39static const int32 kMouseDownWidth = 72;
40static const int32 kMouseDownHeight = 35;
41
42static const int32 kOneButtonOffsets[4]
43	= { 0, kMouseDownWidth };
44static const int32 kTwoButtonOffsets[4]
45	= { 0, kMouseDownWidth / 2, kMouseDownWidth };
46static const int32 kThreeButtonOffsets[4]
47	= { 0, kMouseDownWidth / 3 + 1, kMouseDownWidth / 3 * 2, kMouseDownWidth };
48
49static const rgb_color kButtonTextColor = { 0, 0, 0, 255 };
50static const rgb_color kMouseShadowColor = { 100, 100, 100, 128 };
51static const rgb_color kMouseBodyTopColor = { 0xed, 0xed, 0xed, 255 };
52static const rgb_color kMouseBodyBottomColor = { 0x85, 0x85, 0x85, 255 };
53static const rgb_color kMouseOutlineColor = { 0x51, 0x51, 0x51, 255 };
54static const rgb_color kMouseButtonOutlineColor = { 0xa0, 0xa0, 0xa0, 255 };
55static const rgb_color kButtonPressedColor = { 110, 110, 110, 110 };
56
57
58static const int32*
59getButtonOffsets(int32 type)
60{
61	switch (type) {
62		case 1:
63			return kOneButtonOffsets;
64		case 2:
65			return kTwoButtonOffsets;
66		case 3:
67		default:
68			return kThreeButtonOffsets;
69	}
70}
71
72
73static uint32
74getMappingNumber(int32 mapping)
75{
76	switch (mapping) {
77		case B_PRIMARY_MOUSE_BUTTON:
78			return 0;
79		case B_SECONDARY_MOUSE_BUTTON:
80			return 1;
81		case B_TERTIARY_MOUSE_BUTTON:
82			return 2;
83	}
84	return 0;
85}
86
87
88//	#pragma mark -
89
90
91MouseView::MouseView(const MouseSettings &settings)
92	:
93	BView("mouse_view", B_PULSE_NEEDED | B_WILL_DRAW),
94	fSettings(settings),
95	fType(-1),
96	fButtons(0),
97	fOldButtons(0)
98{
99	SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
100	fScaling = std::max(1.0f, be_plain_font->Size() / 12.0f);
101}
102
103
104MouseView::~MouseView()
105{
106}
107
108
109void
110MouseView::SetMouseType(int32 type)
111{
112	fType = type;
113	Invalidate();
114}
115
116
117void
118MouseView::MouseMapUpdated()
119{
120	Invalidate();
121}
122
123
124void
125MouseView::UpdateFromSettings()
126{
127	SetMouseType(fSettings.MouseType());
128}
129
130
131void
132MouseView::GetPreferredSize(float* _width, float* _height)
133{
134	if (_width != NULL)
135		*_width = fScaling * (kMouseDownWidth + 2);
136	if (_height != NULL)
137		*_height = fScaling * 104;
138}
139
140
141void
142MouseView::AttachedToWindow()
143{
144	AdoptParentColors();
145
146	UpdateFromSettings();
147	_CreateButtonsPicture();
148
149	font_height fontHeight;
150	GetFontHeight(&fontHeight);
151	fDigitHeight = int32(ceilf(fontHeight.ascent) + ceilf(fontHeight.descent));
152	fDigitBaseline = int32(ceilf(fontHeight.ascent));
153}
154
155
156void
157MouseView::MouseUp(BPoint)
158{
159	fButtons = 0;
160	Invalidate(_ButtonsRect());
161	fOldButtons = fButtons;
162}
163
164
165void
166MouseView::MouseDown(BPoint where)
167{
168	BMessage *mouseMsg = Window()->CurrentMessage();
169	fButtons = mouseMsg->FindInt32("buttons");
170	int32 modifiers = mouseMsg->FindInt32("modifiers");
171	if (modifiers & B_CONTROL_KEY) {
172		if (modifiers & B_COMMAND_KEY)
173			fButtons = B_TERTIARY_MOUSE_BUTTON;
174		else
175			fButtons = B_SECONDARY_MOUSE_BUTTON;
176	}
177
178	// Get the current clipping region before requesting any updates.
179	// Otherwise those parts would be excluded from the region.
180	BRegion clipping;
181	GetClippingRegion(&clipping);
182
183	if (fOldButtons != fButtons) {
184		Invalidate(_ButtonsRect());
185		fOldButtons = fButtons;
186	}
187
188	const int32* offset = getButtonOffsets(fType);
189	int32 button = -1;
190	for (int32 i = 0; i <= fType; i++) {
191		if (_ButtonRect(offset, i).Contains(where)) {
192			button = i;
193			break;
194		}
195	}
196	if (button < 0)
197		return;
198
199	// We are setup to receive all mouse events, even if our window
200	// is not active, so make sure that we don't display the menu when
201	// the user clicked inside our view, but another window is on top.
202	if (clipping.Contains(where)) {
203		button = _ConvertFromVisualOrder(button);
204
205		BPopUpMenu menu("Mouse Map Menu");
206		BMessage message(kMsgMouseMap);
207		message.AddInt32("button", button);
208
209		menu.AddItem(new BMenuItem("1", new BMessage(message)));
210		menu.AddItem(new BMenuItem("2", new BMessage(message)));
211		menu.AddItem(new BMenuItem("3", new BMessage(message)));
212
213		menu.ItemAt(getMappingNumber(fSettings.Mapping(button)))
214			->SetMarked(true);
215		menu.SetTargetForItems(Window());
216
217		ConvertToScreen(&where);
218		menu.Go(where, true);
219	}
220}
221
222
223void
224MouseView::Draw(BRect updateFrame)
225{
226	SetDrawingMode(B_OP_ALPHA);
227	SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
228	SetScale(fScaling * 1.8);
229
230	BShape mouseShape;
231	mouseShape.MoveTo(BPoint(16, 12));
232	// left
233	BPoint control[3] = { BPoint(12, 16), BPoint(8, 64), BPoint(32, 64) };
234	mouseShape.BezierTo(control);
235	// right
236	BPoint control2[3] = { BPoint(56, 64), BPoint(52, 16), BPoint(48, 12) };
237	mouseShape.BezierTo(control2);
238	// top
239	BPoint control3[3] = { BPoint(44, 8), BPoint(20, 8), BPoint(16, 12) };
240	mouseShape.BezierTo(control3);
241	mouseShape.Close();
242
243	// Draw the shadow
244	SetOrigin(-17 * fScaling, -11 * fScaling);
245	SetHighColor(kMouseShadowColor);
246	FillShape(&mouseShape, B_SOLID_HIGH);
247
248	// Draw the body
249	SetOrigin(-21 * fScaling, -14 * fScaling);
250	BGradientRadial bodyGradient(28, 24, 128);
251	bodyGradient.AddColor(kMouseBodyTopColor, 0);
252	bodyGradient.AddColor(kMouseBodyBottomColor, 255);
253
254	FillShape(&mouseShape, bodyGradient);
255
256	// Draw the outline
257	SetPenSize(1 / 1.8 / fScaling);
258	SetDrawingMode(B_OP_OVER);
259	SetHighColor(kMouseOutlineColor);
260
261	StrokeShape(&mouseShape, B_SOLID_HIGH);
262
263	// bottom button border
264	BShape buttonsOutline;
265	buttonsOutline.MoveTo(BPoint(13, 27));
266	BPoint control4[] = { BPoint(18, 30), BPoint(46, 30), BPoint(51, 27) };
267	buttonsOutline.BezierTo(control4);
268
269	SetHighColor(kMouseButtonOutlineColor);
270	StrokeShape(&buttonsOutline, B_SOLID_HIGH);
271
272	SetScale(1);
273	SetOrigin(0, 0);
274
275	// Separator between the buttons
276	const int32* offset = getButtonOffsets(fType);
277	for (int32 i = 1; i < fType; i++) {
278		BRect buttonRect = _ButtonRect(offset, i);
279		StrokeLine(buttonRect.LeftTop(), buttonRect.LeftBottom());
280	}
281
282	mouse_map map;
283	fSettings.Mapping(map);
284
285	SetDrawingMode(B_OP_OVER);
286
287	if (fButtons != 0)
288		ClipToPicture(&fButtonsPicture, B_ORIGIN, false);
289
290	for (int32 i = 0; i < fType; i++) {
291		// draw mapping number centered over the button
292
293		bool pressed = (fButtons & map.button[_ConvertFromVisualOrder(i)]) != 0;
294			// is button currently pressed?
295		if (pressed) {
296			SetDrawingMode(B_OP_ALPHA);
297			SetHighColor(kButtonPressedColor);
298			FillRect(_ButtonRect(offset, i));
299		}
300
301		BRect border(fScaling * (offset[i] + 1), fScaling * (kButtonTop + 5),
302			fScaling * offset[i + 1] - 1,
303			fScaling * (kButtonTop + kMouseDownHeight - 4));
304		if (i == 0)
305			border.left += fScaling * 5;
306		if (i == fType - 1)
307			border.right -= fScaling * 4;
308
309		char number[2] = {0};
310		number[0] = getMappingNumber(map.button[_ConvertFromVisualOrder(i)])
311			+ '1';
312
313		SetDrawingMode(B_OP_OVER);
314		SetHighColor(kButtonTextColor);
315		DrawString(number, BPoint(
316			border.left + (border.Width() - StringWidth(number)) / 2,
317			border.top + fDigitBaseline
318				+ (border.IntegerHeight() - fDigitHeight) / 2));
319	}
320
321	if (fButtons != 0)
322		ClipToPicture(NULL);
323}
324
325
326BRect
327MouseView::_ButtonsRect() const
328{
329	return BRect(0, fScaling * kButtonTop, fScaling * kMouseDownWidth,
330			fScaling * (kButtonTop + kMouseDownHeight));
331}
332
333
334BRect
335MouseView::_ButtonRect(const int32* offsets, int index) const
336{
337	return BRect(fScaling * offsets[index], fScaling * kButtonTop,
338		fScaling * offsets[index + 1] - 1,
339		fScaling * (kButtonTop + kMouseDownHeight));
340}
341
342
343int32
344MouseView::_ConvertFromVisualOrder(int32 i)
345{
346	if (fType < 3)
347		return i;
348
349	switch (i) {
350		case 0:
351		default:
352			return 0;
353		case 1:
354			return 2;
355		case 2:
356			return 1;
357	}
358}
359
360
361void
362MouseView::_CreateButtonsPicture()
363{
364	BeginPicture(&fButtonsPicture);
365	SetScale(1.8 * fScaling);
366	SetOrigin(-21 * fScaling, -14 * fScaling);
367
368	BShape mouseShape;
369	mouseShape.MoveTo(BPoint(48, 12));
370	// top
371	BPoint control3[3] = { BPoint(44, 8), BPoint(20, 8), BPoint(16, 12) };
372	mouseShape.BezierTo(control3);
373	// left
374	BPoint control[3] = { BPoint(12, 16), BPoint(13, 27), BPoint(13, 27) };
375	mouseShape.BezierTo(control);
376	// bottom
377	BPoint control4[] = { BPoint(18, 30), BPoint(46, 30), BPoint(51, 27) };
378	mouseShape.BezierTo(control4);
379	// right
380	BPoint control2[3] = { BPoint(51, 27), BPoint(50, 14), BPoint(48, 12) };
381	mouseShape.BezierTo(control2);
382
383	mouseShape.Close();
384
385	SetHighColor(255, 0, 0, 255);
386	FillShape(&mouseShape, B_SOLID_HIGH);
387
388	EndPicture();
389	SetScale(1);
390}
391