1 /*
2  Copyright (C) 2010-2014 Kristian Duske
3 
4  This file is part of TrenchBroom.
5 
6  TrenchBroom is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  TrenchBroom is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "ToolBoxConnector.h"
21 
22 #include "View/ToolBox.h"
23 #include "View/ToolChain.h"
24 
25 namespace TrenchBroom {
26     namespace View {
ToolBoxConnector(wxWindow * window)27         ToolBoxConnector::ToolBoxConnector(wxWindow* window) :
28         m_window(window),
29         m_toolBox(NULL),
30         m_toolChain(new ToolChain()),
31         m_ignoreNextDrag(false) {
32             assert(m_window != NULL);
33             bindEvents();
34         }
35 
~ToolBoxConnector()36         ToolBoxConnector::~ToolBoxConnector() {
37             unbindEvents();
38             delete m_toolChain;
39         }
40 
pickRay() const41         const Ray3& ToolBoxConnector::pickRay() const {
42             return m_inputState.pickRay();
43         }
44 
pickResult() const45         const Model::PickResult& ToolBoxConnector::pickResult() const {
46             return m_inputState.pickResult();
47         }
48 
updatePickResult()49         void ToolBoxConnector::updatePickResult() {
50             assert(m_toolBox != NULL);
51 
52             m_inputState.setPickRequest(doGetPickRequest(m_inputState.mouseX(),  m_inputState.mouseY()));
53             Model::PickResult pickResult = doPick(m_inputState.pickRay());
54             m_toolBox->pick(m_toolChain, m_inputState, pickResult);
55             m_inputState.setPickResult(pickResult);
56         }
57 
updateLastActivation()58         void ToolBoxConnector::updateLastActivation() {
59             assert(m_toolBox != NULL);
60             m_toolBox->updateLastActivation();
61         }
62 
setToolBox(ToolBox & toolBox)63         void ToolBoxConnector::setToolBox(ToolBox& toolBox) {
64             assert(m_toolBox == NULL);
65             m_toolBox = &toolBox;
66         }
67 
addTool(ToolController * tool)68         void ToolBoxConnector::addTool(ToolController* tool) {
69             m_toolChain->append(tool);
70         }
71 
dragEnter(const wxCoord x,const wxCoord y,const String & text)72         bool ToolBoxConnector::dragEnter(const wxCoord x, const wxCoord y, const String& text) {
73             assert(m_toolBox != NULL);
74 
75             mouseMoved(wxPoint(x, y));
76             updatePickResult();
77 
78             const bool result = m_toolBox->dragEnter(m_toolChain, m_inputState, text);
79             m_window->Refresh();
80             return result;
81         }
82 
dragMove(const wxCoord x,const wxCoord y,const String & text)83         bool ToolBoxConnector::dragMove(const wxCoord x, const wxCoord y, const String& text) {
84             assert(m_toolBox != NULL);
85 
86             mouseMoved(wxPoint(x, y));
87             updatePickResult();
88 
89             const bool result = m_toolBox->dragMove(m_toolChain, m_inputState, text);
90             m_window->Refresh();
91             return result;
92         }
93 
dragLeave()94         void ToolBoxConnector::dragLeave() {
95             assert(m_toolBox != NULL);
96 
97             m_toolBox->dragLeave(m_toolChain, m_inputState);
98             m_window->Refresh();
99         }
100 
dragDrop(const wxCoord x,const wxCoord y,const String & text)101         bool ToolBoxConnector::dragDrop(const wxCoord x, const wxCoord y, const String& text) {
102             assert(m_toolBox != NULL);
103 
104             updatePickResult();
105 
106             const bool result = m_toolBox->dragDrop(m_toolChain, m_inputState, text);
107             m_window->Refresh();
108             if (result)
109                 m_window->SetFocus();
110             return result;
111         }
112 
cancel()113         bool ToolBoxConnector::cancel() {
114             assert(m_toolBox != NULL);
115             const bool result = m_toolBox->cancel(m_toolChain);
116             m_inputState.setAnyToolDragging(false);
117             return result;
118         }
119 
setRenderOptions(Renderer::RenderContext & renderContext)120         void ToolBoxConnector::setRenderOptions(Renderer::RenderContext& renderContext) {
121             assert(m_toolBox != NULL);
122             m_toolBox->setRenderOptions(m_toolChain, m_inputState, renderContext);
123         }
124 
renderTools(Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch)125         void ToolBoxConnector::renderTools(Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch) {
126             assert(m_toolBox != NULL);
127             m_toolBox->renderTools(m_toolChain, m_inputState, renderContext, renderBatch);
128         }
129 
bindEvents()130         void ToolBoxConnector::bindEvents() {
131             m_window->Bind(wxEVT_KEY_DOWN, &ToolBoxConnector::OnKey, this);
132             m_window->Bind(wxEVT_KEY_UP, &ToolBoxConnector::OnKey, this);
133             m_window->Bind(wxEVT_LEFT_DOWN, &ToolBoxConnector::OnMouseButton, this);
134             m_window->Bind(wxEVT_LEFT_UP, &ToolBoxConnector::OnMouseButton, this);
135             m_window->Bind(wxEVT_LEFT_DCLICK, &ToolBoxConnector::OnMouseDoubleClick, this);
136             m_window->Bind(wxEVT_RIGHT_DOWN, &ToolBoxConnector::OnMouseButton, this);
137             m_window->Bind(wxEVT_RIGHT_UP, &ToolBoxConnector::OnMouseButton, this);
138             m_window->Bind(wxEVT_RIGHT_DCLICK, &ToolBoxConnector::OnMouseDoubleClick, this);
139             m_window->Bind(wxEVT_MIDDLE_DOWN, &ToolBoxConnector::OnMouseButton, this);
140             m_window->Bind(wxEVT_MIDDLE_UP, &ToolBoxConnector::OnMouseButton, this);
141             m_window->Bind(wxEVT_MIDDLE_DCLICK, &ToolBoxConnector::OnMouseDoubleClick, this);
142             m_window->Bind(wxEVT_AUX1_DOWN, &ToolBoxConnector::OnMouseButton, this);
143             m_window->Bind(wxEVT_AUX1_UP, &ToolBoxConnector::OnMouseButton, this);
144             m_window->Bind(wxEVT_AUX1_DCLICK, &ToolBoxConnector::OnMouseDoubleClick, this);
145             m_window->Bind(wxEVT_AUX2_DOWN, &ToolBoxConnector::OnMouseButton, this);
146             m_window->Bind(wxEVT_AUX2_UP, &ToolBoxConnector::OnMouseButton, this);
147             m_window->Bind(wxEVT_AUX2_DCLICK, &ToolBoxConnector::OnMouseDoubleClick, this);
148             m_window->Bind(wxEVT_MOTION, &ToolBoxConnector::OnMouseMotion, this);
149             m_window->Bind(wxEVT_MOUSEWHEEL, &ToolBoxConnector::OnMouseWheel, this);
150             m_window->Bind(wxEVT_MOUSE_CAPTURE_LOST, &ToolBoxConnector::OnMouseCaptureLost, this);
151             m_window->Bind(wxEVT_SET_FOCUS, &ToolBoxConnector::OnSetFocus, this);
152             m_window->Bind(wxEVT_KILL_FOCUS, &ToolBoxConnector::OnKillFocus, this);
153         }
154 
unbindEvents()155         void ToolBoxConnector::unbindEvents() {
156             m_window->Unbind(wxEVT_KEY_DOWN, &ToolBoxConnector::OnKey, this);
157             m_window->Unbind(wxEVT_KEY_UP, &ToolBoxConnector::OnKey, this);
158             m_window->Unbind(wxEVT_LEFT_DOWN, &ToolBoxConnector::OnMouseButton, this);
159             m_window->Unbind(wxEVT_LEFT_UP, &ToolBoxConnector::OnMouseButton, this);
160             m_window->Unbind(wxEVT_LEFT_DCLICK, &ToolBoxConnector::OnMouseDoubleClick, this);
161             m_window->Unbind(wxEVT_RIGHT_DOWN, &ToolBoxConnector::OnMouseButton, this);
162             m_window->Unbind(wxEVT_RIGHT_UP, &ToolBoxConnector::OnMouseButton, this);
163             m_window->Unbind(wxEVT_RIGHT_DCLICK, &ToolBoxConnector::OnMouseDoubleClick, this);
164             m_window->Unbind(wxEVT_MIDDLE_DOWN, &ToolBoxConnector::OnMouseButton, this);
165             m_window->Unbind(wxEVT_MIDDLE_UP, &ToolBoxConnector::OnMouseButton, this);
166             m_window->Unbind(wxEVT_MIDDLE_DCLICK, &ToolBoxConnector::OnMouseDoubleClick, this);
167             m_window->Unbind(wxEVT_AUX1_DOWN, &ToolBoxConnector::OnMouseButton, this);
168             m_window->Unbind(wxEVT_AUX1_UP, &ToolBoxConnector::OnMouseButton, this);
169             m_window->Unbind(wxEVT_AUX1_DCLICK, &ToolBoxConnector::OnMouseDoubleClick, this);
170             m_window->Unbind(wxEVT_AUX2_DOWN, &ToolBoxConnector::OnMouseButton, this);
171             m_window->Unbind(wxEVT_AUX2_UP, &ToolBoxConnector::OnMouseButton, this);
172             m_window->Unbind(wxEVT_AUX2_DCLICK, &ToolBoxConnector::OnMouseDoubleClick, this);
173             m_window->Unbind(wxEVT_MOTION, &ToolBoxConnector::OnMouseMotion, this);
174             m_window->Unbind(wxEVT_MOUSEWHEEL, &ToolBoxConnector::OnMouseWheel, this);
175             m_window->Unbind(wxEVT_MOUSE_CAPTURE_LOST, &ToolBoxConnector::OnMouseCaptureLost, this);
176             m_window->Unbind(wxEVT_SET_FOCUS, &ToolBoxConnector::OnSetFocus, this);
177             m_window->Unbind(wxEVT_KILL_FOCUS, &ToolBoxConnector::OnKillFocus, this);
178         }
179 
OnKey(wxKeyEvent & event)180         void ToolBoxConnector::OnKey(wxKeyEvent& event) {
181             if (m_window->IsBeingDeleted()) return;
182 
183             assert(m_toolBox != NULL);
184 
185             event.Skip();
186             updateModifierKeys();
187             m_window->Refresh();
188         }
189 
OnMouseButton(wxMouseEvent & event)190         void ToolBoxConnector::OnMouseButton(wxMouseEvent& event) {
191             if (m_window->IsBeingDeleted()) return;
192 
193             assert(m_toolBox != NULL);
194 
195             event.Skip();
196 
197             const MouseButtonState button = mouseButton(event);
198             if (m_toolBox->ignoreNextClick() && button == MouseButtons::MBLeft) {
199                 if (event.ButtonUp())
200                     m_toolBox->clearIgnoreNextClick();
201                 return;
202             }
203 
204             m_window->SetFocus();
205             if (event.ButtonUp())
206                 m_toolBox->clearIgnoreNextClick();
207 
208             updateModifierKeys();
209             if (event.ButtonDown()) {
210                 captureMouse();
211                 m_clickTime = wxGetLocalTimeMillis();
212                 m_clickPos = event.GetPosition();
213                 m_inputState.mouseDown(button);
214                 m_toolBox->mouseDown(m_toolChain, m_inputState);
215             } else {
216                 if (m_toolBox->dragging()) {
217                     const wxLongLong clickInterval = wxGetLocalTimeMillis() - m_clickTime;
218                     if (clickInterval <= 100) {
219                         m_toolBox->cancelMouseDrag();
220                         m_toolBox->mouseUp(m_toolChain, m_inputState);
221                         m_toolBox->mouseClick(m_toolChain, m_inputState);
222                     } else {
223                         m_toolBox->endMouseDrag(m_inputState);
224                         m_toolBox->mouseUp(m_toolChain, m_inputState);
225                     }
226                     m_inputState.mouseUp(button);
227                     m_inputState.setAnyToolDragging(false);
228                     releaseMouse();
229                 } else if (!m_ignoreNextDrag) {
230                     m_toolBox->mouseUp(m_toolChain, m_inputState);
231                     const bool handled = isWithinClickDistance(event.GetPosition()) && m_toolBox->mouseClick(m_toolChain, m_inputState);
232                     m_inputState.mouseUp(button);
233                     releaseMouse();
234 
235                     if (button == MouseButtons::MBRight && !handled) {
236                         // We miss mouse events when a popup menu is already open, so we must make sure that the input
237                         // state is up to date.
238                         mouseMoved(event.GetPosition());
239                         updatePickResult();
240                         showPopupMenu();
241                     }
242                 } else {
243                     m_toolBox->mouseUp(m_toolChain, m_inputState);
244                     m_inputState.mouseUp(button);
245                     releaseMouse();
246                 }
247             }
248 
249             updatePickResult();
250             m_ignoreNextDrag = false;
251 
252             m_window->Refresh();
253         }
254 
OnMouseDoubleClick(wxMouseEvent & event)255         void ToolBoxConnector::OnMouseDoubleClick(wxMouseEvent& event) {
256             if (m_window->IsBeingDeleted()) return;
257 
258             assert(m_toolBox != NULL);
259 
260             event.Skip();
261 
262             const MouseButtonState button = mouseButton(event);
263             updateModifierKeys();
264 
265             m_clickPos = event.GetPosition();
266             m_inputState.mouseDown(button);
267             m_toolBox->mouseDoubleClick(m_toolChain, m_inputState);
268             m_inputState.mouseUp(button);
269 
270             updatePickResult();
271 
272             m_window->Refresh();
273         }
274 
OnMouseMotion(wxMouseEvent & event)275         void ToolBoxConnector::OnMouseMotion(wxMouseEvent& event) {
276             if (m_window->IsBeingDeleted()) return;
277 
278             assert(m_toolBox != NULL);
279 
280             event.Skip();
281 
282             updateModifierKeys();
283             if (m_toolBox->dragging()) {
284                 mouseMoved(event.GetPosition());
285                 updatePickResult();
286                 if (!m_toolBox->mouseDrag(m_inputState)) {
287                     m_toolBox->endMouseDrag(m_inputState);
288                     m_inputState.setAnyToolDragging(false);
289                     m_ignoreNextDrag = true;
290                 }
291             } else if (!m_ignoreNextDrag) {
292                 if (m_inputState.mouseButtons() != MouseButtons::MBNone) {
293                     if (!isWithinClickDistance(event.GetPosition())) {
294                         const bool dragStarted = m_toolBox->startMouseDrag(m_toolChain, m_inputState);
295                         if (dragStarted)
296                             m_ignoreNextDrag = true;
297                         mouseMoved(event.GetPosition());
298                         updatePickResult();
299                         if (dragStarted) {
300                             m_inputState.setAnyToolDragging(true);
301                             m_toolBox->mouseDrag(m_inputState);
302                         }
303                     }
304                 } else {
305                     mouseMoved(event.GetPosition());
306                     updatePickResult();
307                     m_toolBox->mouseMove(m_toolChain, m_inputState);
308                 }
309             }
310 
311             m_window->Refresh();
312 			m_window->Update(); // neccessary for smooth rendering on Windows
313         }
314 
OnMouseWheel(wxMouseEvent & event)315         void ToolBoxConnector::OnMouseWheel(wxMouseEvent& event) {
316             if (m_window->IsBeingDeleted()) return;
317 
318             assert(m_toolBox != NULL);
319 
320             event.Skip();
321 
322             updateModifierKeys();
323             const float delta = static_cast<float>(event.GetWheelRotation()) / event.GetWheelDelta() * event.GetLinesPerAction();
324             if (event.GetWheelAxis() == wxMOUSE_WHEEL_HORIZONTAL)
325                 m_inputState.scroll(delta, 0.0f);
326             else if (event.GetWheelAxis() == wxMOUSE_WHEEL_VERTICAL)
327                 m_inputState.scroll(0.0f, delta);
328             m_toolBox->mouseScroll(m_toolChain, m_inputState);
329 
330             updatePickResult();
331             m_window->Refresh();
332         }
333 
334 
OnMouseCaptureLost(wxMouseCaptureLostEvent & event)335         void ToolBoxConnector::OnMouseCaptureLost(wxMouseCaptureLostEvent& event) {
336             if (m_window->IsBeingDeleted()) return;
337 
338             assert(m_toolBox != NULL);
339 
340             event.Skip();
341 
342             cancelDrag();
343             m_window->Refresh();
344         }
345 
OnSetFocus(wxFocusEvent & event)346         void ToolBoxConnector::OnSetFocus(wxFocusEvent& event) {
347             if (m_window->IsBeingDeleted()) return;
348 
349             assert(m_toolBox != NULL);
350 
351             event.Skip();
352             updateModifierKeys();
353             m_window->Refresh();
354 
355             mouseMoved(m_window->ScreenToClient(wxGetMousePosition()));
356         }
357 
OnKillFocus(wxFocusEvent & event)358         void ToolBoxConnector::OnKillFocus(wxFocusEvent& event) {
359             if (m_window->IsBeingDeleted()) return;
360 
361             assert(m_toolBox != NULL);
362 
363             event.Skip();
364 
365             cancelDrag();
366             releaseMouse();
367             updateModifierKeys();
368             m_window->Refresh();
369         }
370 
isWithinClickDistance(const wxPoint & pos) const371         bool ToolBoxConnector::isWithinClickDistance(const wxPoint& pos) const {
372             return (std::abs(pos.x - m_clickPos.x) <= 1 &&
373                     std::abs(pos.y - m_clickPos.y) <= 1);
374         }
375 
captureMouse()376         void ToolBoxConnector::captureMouse() {
377             if (!m_window->HasCapture() && !m_toolBox->dragging())
378                 m_window->CaptureMouse();
379         }
380 
releaseMouse()381         void ToolBoxConnector::releaseMouse() {
382             if (m_window->HasCapture() && !m_toolBox->dragging())
383                 m_window->ReleaseMouse();
384         }
385 
386 
cancelDrag()387         void ToolBoxConnector::cancelDrag() {
388             if (m_toolBox->dragging()) {
389                 m_toolBox->cancelMouseDrag();
390                 m_inputState.setAnyToolDragging(false);
391                 m_inputState.clearMouseButtons();
392             }
393         }
394 
modifierKeys()395         ModifierKeyState ToolBoxConnector::modifierKeys() {
396             const wxMouseState mouseState = wxGetMouseState();
397 
398             ModifierKeyState state = ModifierKeys::MKNone;
399             if (mouseState.CmdDown())
400                 state |= ModifierKeys::MKCtrlCmd;
401             if (mouseState.ShiftDown())
402                 state |= ModifierKeys::MKShift;
403             if (mouseState.AltDown())
404                 state |= ModifierKeys::MKAlt;
405             return state;
406         }
407 
setModifierKeys()408         bool ToolBoxConnector::setModifierKeys() {
409             const ModifierKeyState keys = modifierKeys();
410             if (keys != m_inputState.modifierKeys()) {
411                 m_inputState.setModifierKeys(keys);
412                 return true;
413             }
414             return false;
415         }
416 
clearModifierKeys()417         bool ToolBoxConnector::clearModifierKeys() {
418             if (m_inputState.modifierKeys() != ModifierKeys::MKNone) {
419                 m_inputState.setModifierKeys(ModifierKeys::MKNone);
420                 return true;
421             }
422             return false;
423         }
424 
updateModifierKeys()425         void ToolBoxConnector::updateModifierKeys() {
426             if (setModifierKeys()) {
427                 updatePickResult();
428                 m_toolBox->modifierKeyChange(m_toolChain, m_inputState);
429             }
430         }
431 
mouseButton(wxMouseEvent & event)432         MouseButtonState ToolBoxConnector::mouseButton(wxMouseEvent& event) {
433             switch (event.GetButton()) {
434                 case wxMOUSE_BTN_LEFT:
435                     return MouseButtons::MBLeft;
436                 case wxMOUSE_BTN_MIDDLE:
437                     return MouseButtons::MBMiddle;
438                 case wxMOUSE_BTN_RIGHT:
439                     return MouseButtons::MBRight;
440                 default:
441                     return MouseButtons::MBNone;
442             }
443         }
444 
mouseMoved(const wxPoint & position)445         void ToolBoxConnector::mouseMoved(const wxPoint& position) {
446             const wxPoint delta = position - m_lastMousePos;
447             m_inputState.mouseMove(position.x, position.y, delta.x, delta.y);
448             m_lastMousePos = position;
449         }
450 
showPopupMenu()451         void ToolBoxConnector::showPopupMenu() {
452             doShowPopupMenu();
453             updateModifierKeys();
454         }
455 
doShowPopupMenu()456         void ToolBoxConnector::doShowPopupMenu() {}
457     }
458 }
459