1/*
2 * Copyright 2003-2009 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 <Box.h>
16#include <Button.h>
17#include <Debug.h>
18#include <GradientLinear.h>
19#include <MenuField.h>
20#include <MenuItem.h>
21#include <PopUpMenu.h>
22#include <Region.h>
23#include <Slider.h>
24#include <TextControl.h>
25#include <TranslationUtils.h>
26#include <TranslatorFormats.h>
27#include <Window.h>
28
29#include "MouseConstants.h"
30#include "MouseSettings.h"
31#include "MouseWindow.h"
32
33static const int32 kButtonTop = 6;
34static const int32 kMouseDownWidth = 72;
35static const int32 kMouseDownHeight = 30;
36
37static const int32 kOneButtonOffsets[4]
38	= { 0, kMouseDownWidth };
39static const int32 kTwoButtonOffsets[4]
40	= { 0, kMouseDownWidth / 2, kMouseDownWidth };
41static const int32 kThreeButtonOffsets[4]
42	= { 0, kMouseDownWidth / 3, kMouseDownWidth / 3 * 2, kMouseDownWidth };
43
44static const rgb_color kButtonPressedColor = { 120, 120, 120 };
45static const rgb_color kButtonReleasedLeftColor = { 255, 255, 255 };
46static const rgb_color kButtonReleasedRightColor = { 184, 184, 184 };
47static const rgb_color kButtonPressedSeparatorColor = { 48, 48, 48 };
48static const rgb_color kButtonReleasedSeparatorColor = { 88, 88, 88 };
49static const rgb_color kButtonTextColor = { 32, 32, 32 };
50static const rgb_color kMouseShadowColor = { 150, 150, 150 };
51static const rgb_color kMouseBodyTopColor = { 210, 210, 210 };
52static const rgb_color kMouseBodyBottomColor = { 140, 140, 140 };
53static const rgb_color kMouseOutlineColor = { 0, 0, 0 };
54
55static const int32*
56getButtonOffsets(int32 type)
57{
58	switch (type) {
59		case 1:
60			return kOneButtonOffsets;
61		case 2:
62			return kTwoButtonOffsets;
63		case 3:
64		default:
65			return kThreeButtonOffsets;
66	}
67}
68
69
70static uint32
71getMappingNumber(int32 mapping)
72{
73	switch (mapping) {
74		case B_PRIMARY_MOUSE_BUTTON:
75			return 0;
76		case B_SECONDARY_MOUSE_BUTTON:
77			return 1;
78		case B_TERTIARY_MOUSE_BUTTON:
79			return 2;
80	}
81	return 0;
82}
83
84
85//	#pragma mark -
86
87
88MouseView::MouseView(const MouseSettings &settings)
89	:
90	BView("mouse_view", B_PULSE_NEEDED | B_WILL_DRAW),
91	fSettings(settings),
92	fType(-1),
93	fButtons(0),
94	fOldButtons(0)
95{
96	SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
97}
98
99
100MouseView::~MouseView()
101{
102}
103
104
105void
106MouseView::GetPreferredSize(float* _width, float* _height)
107{
108	if (_width)
109		*_width = kMouseDownWidth + 2;
110	if (_height)
111		*_height = 100;
112}
113
114
115void
116MouseView::MouseUp(BPoint)
117{
118	fButtons = 0;
119	Invalidate(BRect(0, kButtonTop, kMouseDownWidth,
120		kButtonTop + kMouseDownHeight));
121	fOldButtons = fButtons;
122}
123
124
125void
126MouseView::MouseDown(BPoint where)
127{
128	BMessage *mouseMsg = Window()->CurrentMessage();
129	fButtons = mouseMsg->FindInt32("buttons");
130	int32 modifiers = mouseMsg->FindInt32("modifiers");
131	if (modifiers & B_CONTROL_KEY) {
132		if (modifiers & B_COMMAND_KEY)
133			fButtons = B_TERTIARY_MOUSE_BUTTON;
134		else
135			fButtons = B_SECONDARY_MOUSE_BUTTON;
136	}
137
138	// Get the current clipping region before requesting any updates.
139	// Otherwise those parts would be excluded from the region.
140	BRegion clipping;
141	GetClippingRegion(&clipping);
142
143	if (fOldButtons != fButtons) {
144		Invalidate(BRect(0, kButtonTop, kMouseDownWidth, kButtonTop
145			+ kMouseDownHeight));
146		fOldButtons = fButtons;
147	}
148
149	const int32* offset = getButtonOffsets(fType);
150	int32 button = -1;
151	for (int32 i = 0; i <= fType; i++) {
152		BRect frame(offset[i], kButtonTop, offset[i + 1] - 1,
153			kButtonTop + kMouseDownHeight);
154		if (frame.Contains(where)) {
155			button = i;
156			break;
157		}
158	}
159	if (button < 0)
160		return;
161
162	// We are setup to receive all mouse events, even if our window
163	// is not active, so make sure that we don't display the menu when
164	// the user clicked inside our view, but another window is on top.
165	if (clipping.Contains(where)) {
166		button = _ConvertFromVisualOrder(button);
167
168		BPopUpMenu menu("Mouse Map Menu");
169		BMessage message(kMsgMouseMap);
170		message.AddInt32("button", button);
171
172		menu.AddItem(new BMenuItem("1", new BMessage(message)));
173		menu.AddItem(new BMenuItem("2", new BMessage(message)));
174		menu.AddItem(new BMenuItem("3", new BMessage(message)));
175
176		menu.ItemAt(getMappingNumber(fSettings.Mapping(button)))
177			->SetMarked(true);
178		menu.SetTargetForItems(Window());
179
180		ConvertToScreen(&where);
181		menu.Go(where, true);
182	}
183}
184
185
186void
187MouseView::AttachedToWindow()
188{
189	if (Parent() != NULL)
190		SetViewColor(Parent()->ViewColor());
191	else
192		SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
193
194	UpdateFromSettings();
195}
196
197
198void
199MouseView::Draw(BRect updateFrame)
200{
201	BRect mouseBody(Bounds());
202	mouseBody.right -= 2;
203	mouseBody.bottom -= 2;
204	float radius = 10;
205
206	// Draw the shadow
207	SetHighColor(kMouseShadowColor);
208	FillRoundRect(mouseBody.OffsetByCopy(2, 2), radius, radius);
209
210	// Draw the body
211	BGradientLinear gradient(mouseBody.LeftTop(), mouseBody.LeftBottom());
212	gradient.AddColor(kMouseBodyTopColor, 0);
213	gradient.AddColor(kMouseBodyBottomColor, 255);
214	FillRoundRect(mouseBody, radius, radius, gradient);
215
216	// Draw the outline
217	SetHighColor(kMouseOutlineColor);
218	StrokeRoundRect(mouseBody, radius, radius);
219
220	mouse_map map;
221	fSettings.Mapping(map);
222
223	const int32* offset = getButtonOffsets(fType);
224	bool middlePressed = fType == 3 && (map.button[2] & fButtons) != 0;
225
226	for (int32 i = 0; i < fType; i++) {
227		BRect border(offset[i] + 1, kButtonTop + 2, offset[i + 1] - 1,
228			kButtonTop + kMouseDownHeight - 4);
229		bool pressed = (fButtons & map.button[_ConvertFromVisualOrder(i)]) != 0;
230			// is button currently pressed?
231
232		if (pressed) {
233			BRect frame(offset[i], 0, offset[i + 1], kMouseDownHeight - 1);
234			frame.InsetBy(1, 1);
235			SetHighColor(kButtonPressedColor);
236			FillRect(frame.OffsetByCopy(0, kButtonTop));
237		}
238
239		SetDrawingMode(B_OP_OVER);
240
241		if (i > 0 && fType > i) {
242			// left border
243			SetHighColor(pressed ?
244				kButtonPressedColor : kButtonReleasedLeftColor);
245			StrokeLine(BPoint(border.LeftTop()), BPoint(border.LeftBottom()));
246
247			// draw separator
248			BRect separator = border.OffsetByCopy(-1, -1);
249			separator.bottom += 2;
250			if (!middlePressed)
251				border.top++;
252
253			SetHighColor(middlePressed ?
254				kButtonPressedSeparatorColor : kButtonReleasedSeparatorColor);
255			StrokeLine(BPoint(separator.LeftTop()),
256				BPoint(separator.LeftBottom()));
257		}
258
259		if (fType > 1 && i + 1 < fType) {
260			// right border
261			SetHighColor(pressed ?
262				kButtonPressedColor : kButtonReleasedRightColor);
263			StrokeLine(BPoint(border.RightTop()),
264				BPoint(border.RightBottom()));
265		}
266
267		// draw mapping number centered over the button
268
269		SetHighColor(kButtonTextColor);
270
271		char number[2] = {0};
272		number[0] = getMappingNumber(map.button[_ConvertFromVisualOrder(i)])
273			+ '1';
274
275		DrawString(number, BPoint(border.left +
276			(border.Width() - StringWidth(number)) / 2, kButtonTop + 18));
277	}
278}
279
280
281int32
282MouseView::_ConvertFromVisualOrder(int32 i)
283{
284	if (fType < 3)
285		return i;
286
287	switch (i) {
288		case 0:
289		default:
290			return 0;
291		case 1:
292			return 2;
293		case 2:
294			return 1;
295	}
296}
297
298
299void
300MouseView::SetMouseType(int32 type)
301{
302	fType = type;
303	Invalidate();
304}
305
306
307void
308MouseView::MouseMapUpdated()
309{
310	Invalidate();
311}
312
313
314void
315MouseView::UpdateFromSettings()
316{
317	SetMouseType(fSettings.MouseType());
318}
319
320