Merge pull request #4615 from bombizombi/master
[vuplus_xbmc] / xbmc / input / MouseStat.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "MouseStat.h"
22 #include "guilib/Key.h"
23 #include "settings/lib/Setting.h"
24 #include "utils/TimeUtils.h"
25 #include "windowing/WindowingFactory.h"
26
27 CMouseStat::CMouseStat()
28 {
29   m_pointerState = MOUSE_STATE_NORMAL;
30   SetEnabled();
31   m_speedX = m_speedY = 0;
32   m_maxX = m_maxY = 0;
33   memset(&m_mouseState, 0, sizeof(m_mouseState));
34   m_Action = ACTION_NOOP;
35 }
36
37 CMouseStat::~CMouseStat()
38 {
39 }
40
41 void CMouseStat::OnSettingChanged(const CSetting *setting)
42 {
43   if (setting == NULL)
44     return;
45
46   const std::string &settingId = setting->GetId();
47   if (settingId == "input.enablemouse")
48     SetEnabled(((CSettingBool*)setting)->GetValue());
49 }
50
51 void CMouseStat::Initialize()
52 {
53   // Set the default resolution (PAL)
54   SetResolution(720, 576, 1, 1);
55 }
56
57 void CMouseStat::HandleEvent(XBMC_Event& newEvent)
58 {
59   // Save the mouse position and the size of the last move
60   int dx, dy;
61   if (newEvent.type == XBMC_MOUSEMOTION)
62   {
63     dx = newEvent.motion.x - m_mouseState.x;
64     dy = newEvent.motion.y - m_mouseState.y;
65   }
66   else if (newEvent.type == XBMC_MOUSEBUTTONDOWN || newEvent.type == XBMC_MOUSEBUTTONUP)
67   {
68     dx = newEvent.button.x - m_mouseState.x;
69     dy = newEvent.button.y - m_mouseState.y;
70   }
71   else
72   {
73     return;
74   }
75   m_mouseState.dx = dx;
76   m_mouseState.dy = dy;
77   m_mouseState.x  = std::max(0, std::min(m_maxX, m_mouseState.x + dx));
78   m_mouseState.y  = std::max(0, std::min(m_maxY, m_mouseState.y + dy));
79
80   // Fill in the public members
81   if (newEvent.button.type == XBMC_MOUSEBUTTONDOWN)
82   {
83     if (newEvent.button.button == XBMC_BUTTON_LEFT) m_mouseState.button[MOUSE_LEFT_BUTTON] = true;
84     if (newEvent.button.button == XBMC_BUTTON_RIGHT) m_mouseState.button[MOUSE_RIGHT_BUTTON] = true;
85     if (newEvent.button.button == XBMC_BUTTON_MIDDLE) m_mouseState.button[MOUSE_MIDDLE_BUTTON] = true;
86     if (newEvent.button.button == XBMC_BUTTON_X1) m_mouseState.button[MOUSE_EXTRA_BUTTON1] = true;
87     if (newEvent.button.button == XBMC_BUTTON_X2) m_mouseState.button[MOUSE_EXTRA_BUTTON2] = true;
88     if (newEvent.button.button == XBMC_BUTTON_WHEELUP) m_mouseState.dz = 1;
89     if (newEvent.button.button == XBMC_BUTTON_WHEELDOWN) m_mouseState.dz = -1;
90   }
91   else if (newEvent.button.type == XBMC_MOUSEBUTTONUP)
92   {
93     if (newEvent.button.button == XBMC_BUTTON_LEFT) m_mouseState.button[MOUSE_LEFT_BUTTON] = false;
94     if (newEvent.button.button == XBMC_BUTTON_RIGHT) m_mouseState.button[MOUSE_RIGHT_BUTTON] = false;
95     if (newEvent.button.button == XBMC_BUTTON_MIDDLE) m_mouseState.button[MOUSE_MIDDLE_BUTTON] = false;
96     if (newEvent.button.button == XBMC_BUTTON_X1) m_mouseState.button[MOUSE_EXTRA_BUTTON1] = false;
97     if (newEvent.button.button == XBMC_BUTTON_X2) m_mouseState.button[MOUSE_EXTRA_BUTTON2] = false;
98     if (newEvent.button.button == XBMC_BUTTON_WHEELUP) m_mouseState.dz = 0;
99     if (newEvent.button.button == XBMC_BUTTON_WHEELDOWN) m_mouseState.dz = 0;
100   }
101
102   // Now check the current message and the previous state to find out if
103   // this is a click, doubleclick, drag etc
104   uint32_t now = CTimeUtils::GetFrameTime();
105   bool bNothingDown = true;
106   
107   for (int i = 0; i < 5; i++)
108   {
109     bClick[i] = false;
110     bDoubleClick[i] = false;
111     bHold[i] = 0;
112
113     // CButtonState::Update does the hard work of checking the button state
114     // and spotting drags, doubleclicks etc
115     CButtonState::BUTTON_ACTION action = m_buttonState[i].Update(now, m_mouseState.x, m_mouseState.y, m_mouseState.button[i]);
116     switch (action)
117     {
118     case CButtonState::MB_SHORT_CLICK:
119     case CButtonState::MB_LONG_CLICK:
120       bClick[i] = true;
121       bNothingDown = false;
122       break;
123     case CButtonState::MB_DOUBLE_CLICK:
124       bDoubleClick[i] = true;
125       bNothingDown = false;
126       break;
127     case CButtonState::MB_DRAG_START:
128     case CButtonState::MB_DRAG:
129     case CButtonState::MB_DRAG_END:
130       bHold[i] = action - CButtonState::MB_DRAG_START + 1;
131       bNothingDown = false;
132       break;
133     default:
134       break;
135     }
136   }
137
138   // Now work out what action ID to send to XBMC.
139   // The bClick array is set true if CButtonState::Update spots a click
140   // i.e. a button down followed by a button up.
141   if (bClick[MOUSE_LEFT_BUTTON])
142     m_Action = ACTION_MOUSE_LEFT_CLICK;
143   else if (bClick[MOUSE_RIGHT_BUTTON])
144     m_Action = ACTION_MOUSE_RIGHT_CLICK;
145   else if (bClick[MOUSE_MIDDLE_BUTTON])
146     m_Action = ACTION_MOUSE_MIDDLE_CLICK;
147
148   // The bDoubleClick array is set true if CButtonState::Update spots a
149   // button down within double_click_time (500ms) of the last click
150   else if (bDoubleClick[MOUSE_LEFT_BUTTON])
151     m_Action = ACTION_MOUSE_DOUBLE_CLICK;
152
153   // The bHold array is set true if CButtonState::Update spots a mouse drag
154   else if (bHold[MOUSE_LEFT_BUTTON])
155     m_Action = ACTION_MOUSE_DRAG;
156
157   // dz is +1 on wheel up and -1 on wheel down
158   else if (m_mouseState.dz > 0)
159     m_Action = ACTION_MOUSE_WHEEL_UP;
160   else if (m_mouseState.dz < 0)
161     m_Action = ACTION_MOUSE_WHEEL_DOWN;
162
163   // Check for a mouse move that isn't a drag, ignoring messages with no movement at all
164   else if (newEvent.type == XBMC_MOUSEMOTION && (m_mouseState.dx || m_mouseState.dy))
165     m_Action = ACTION_MOUSE_MOVE;
166
167   // ignore any other mouse messages
168   else
169     m_Action = ACTION_NOOP;
170
171   // activate the mouse pointer if we have an action or the mouse has moved far enough
172   if ((MovedPastThreshold() && m_Action == ACTION_MOUSE_MOVE) ||
173       (m_Action != ACTION_NOOP && m_Action != ACTION_MOUSE_MOVE))
174     SetActive();
175
176   // reset the mouse state if nothing is held down
177   if (bNothingDown)
178     SetState(MOUSE_STATE_NORMAL);
179 }
180
181 void CMouseStat::SetResolution(int maxX, int maxY, float speedX, float speedY)
182 {
183   m_maxX = maxX;
184   m_maxY = maxY;
185
186   // speed is currently unused
187   m_speedX = speedX;
188   m_speedY = speedY;
189 }
190
191 void CMouseStat::SetActive(bool active /*=true*/)
192 {
193   m_lastActiveTime = CTimeUtils::GetFrameTime();
194   m_mouseState.active = active;
195   // we show the OS mouse if:
196   // 1. The mouse is active (it has been moved) AND
197   // 2. The XBMC mouse is disabled in settings AND
198   // 3. XBMC is not in fullscreen.
199   g_Windowing.ShowOSMouse(m_mouseState.active && !IsEnabled() && !g_Windowing.IsFullScreen());
200 }
201
202 // IsActive - returns true if we have been active in the last MOUSE_ACTIVE_LENGTH period
203 bool CMouseStat::IsActive()
204 {
205   if (m_mouseState.active && (CTimeUtils::GetFrameTime() - m_lastActiveTime > MOUSE_ACTIVE_LENGTH))
206     SetActive(false);
207   return (m_mouseState.active && IsEnabled());
208 }
209
210 void CMouseStat::SetEnabled(bool enabled)
211 {
212   m_mouseEnabled = enabled;
213   SetActive(enabled);
214 }
215
216 // IsEnabled - returns true if mouse is enabled
217 bool CMouseStat::IsEnabled() const
218 {
219   return m_mouseEnabled;
220 }
221
222 bool CMouseStat::MovedPastThreshold() const
223 {
224   return (m_mouseState.dx * m_mouseState.dx + m_mouseState.dy * m_mouseState.dy >= MOUSE_MINIMUM_MOVEMENT * MOUSE_MINIMUM_MOVEMENT);
225 }
226
227 uint32_t CMouseStat::GetAction() const
228 {
229   return m_Action;
230 }
231
232 int CMouseStat::GetHold(int ButtonID) const
233 {
234   switch (ButtonID)
235   { case MOUSE_LEFT_BUTTON:
236       return bHold[MOUSE_LEFT_BUTTON];
237   }
238   return false;
239 }
240
241 CMouseStat::CButtonState::CButtonState()
242 {
243   m_state = STATE_RELEASED;
244   m_time = 0;
245   m_x = 0;
246   m_y = 0;
247 }
248
249 bool CMouseStat::CButtonState::InClickRange(int x, int y) const
250 {
251   int dx = x - m_x;
252   int dy = y - m_y;
253   return (unsigned int)(dx*dx + dy*dy) <= click_confines*click_confines;
254 }
255
256 CMouseStat::CButtonState::BUTTON_ACTION CMouseStat::CButtonState::Update(unsigned int time, int x, int y, bool down)
257 {
258   if (m_state == STATE_IN_DRAG)
259   {
260     if (down)
261       return MB_DRAG;
262     m_state = STATE_RELEASED;
263     return MB_DRAG_END;
264   }
265   else if (m_state == STATE_RELEASED)
266   {
267     if (down)
268     {
269       m_state = STATE_IN_CLICK;
270       m_time = time;
271       m_x = x;
272       m_y = y;
273     }
274   }
275   else if (m_state == STATE_IN_CLICK)
276   {
277     if (down)
278     {
279       if (!InClickRange(x,y))
280       { // beginning a drag
281         m_state = STATE_IN_DRAG;
282         return MB_DRAG_START;
283       }
284     }
285     else
286     { // button up
287       if (time - m_time < short_click_time)
288       { // single click
289         m_state = STATE_IN_DOUBLE_CLICK;
290         m_time = time; // double click time and positioning is measured from the
291         m_x = x;       // end of a single click
292         m_y = y;
293         return MB_SHORT_CLICK;
294       }
295       else
296       { // long click
297         m_state = STATE_RELEASED;
298         return MB_LONG_CLICK;
299       }
300     }
301   }
302   else if (m_state == STATE_IN_DOUBLE_CLICK)
303   {
304     if (time - m_time > double_click_time || !InClickRange(x,y))
305     { // too long, or moved to much - reset to released state and re-update, as we may be starting a new click
306       m_state = STATE_RELEASED;
307       return Update(time, x, y, down);
308     }
309     if (down)
310     {
311       m_state = STATE_IN_DOUBLE_IGNORE;
312       return MB_DOUBLE_CLICK;
313     }
314   }
315   else if (m_state == STATE_IN_DOUBLE_IGNORE)
316   {
317     if (!down)
318       m_state = STATE_RELEASED;
319   }
320
321   return MB_NONE;
322 }
323