]> git.saurik.com Git - wxWidgets.git/blob - src/common/popupcmn.cpp
09d012c6c140cf91336268fb9e3526089f89ff66
[wxWidgets.git] / src / common / popupcmn.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/popupcmn.cpp
3 // Purpose: implementation of wxPopupTransientWindow
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 06.01.01
7 // Copyright: (c) 2001 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10
11 // ============================================================================
12 // declarations
13 // ============================================================================
14
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #if wxUSE_POPUPWIN
27
28 #include "wx/popupwin.h"
29
30 #ifndef WX_PRECOMP
31 #include "wx/combobox.h" // wxComboCtrl
32 #include "wx/app.h" // wxPostEvent
33 #include "wx/log.h"
34 #endif //WX_PRECOMP
35
36 #include "wx/display.h"
37 #include "wx/recguard.h"
38
39 #ifdef __WXUNIVERSAL__
40 #include "wx/univ/renderer.h"
41 #include "wx/scrolbar.h"
42 #endif // __WXUNIVERSAL__
43
44 #ifdef __WXGTK__
45 #include <gtk/gtk.h>
46 #if GTK_CHECK_VERSION(2,0,0)
47 #include "wx/gtk/private/gtk2-compat.h"
48 #else
49 #define gtk_widget_get_window(x) x->window
50 #endif
51 #elif defined(__WXMSW__)
52 #include "wx/msw/private.h"
53 #elif defined(__WXX11__)
54 #include "wx/x11/private.h"
55 #endif
56
57 IMPLEMENT_DYNAMIC_CLASS(wxPopupWindow, wxWindow)
58 IMPLEMENT_DYNAMIC_CLASS(wxPopupTransientWindow, wxPopupWindow)
59
60 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
61 IMPLEMENT_DYNAMIC_CLASS(wxPopupComboWindow, wxPopupTransientWindow)
62 #endif
63
64 // ----------------------------------------------------------------------------
65 // private classes
66 // ----------------------------------------------------------------------------
67
68 // event handlers which we use to intercept events which cause the popup to
69 // disappear
70 class wxPopupWindowHandler : public wxEvtHandler
71 {
72 public:
73 wxPopupWindowHandler(wxPopupTransientWindow *popup) : m_popup(popup) {}
74
75 protected:
76 // event handlers
77 void OnLeftDown(wxMouseEvent& event);
78 void OnCaptureLost(wxMouseCaptureLostEvent& event);
79
80 private:
81 wxPopupTransientWindow *m_popup;
82
83 DECLARE_EVENT_TABLE()
84 wxDECLARE_NO_COPY_CLASS(wxPopupWindowHandler);
85 };
86
87 class wxPopupFocusHandler : public wxEvtHandler
88 {
89 public:
90 wxPopupFocusHandler(wxPopupTransientWindow *popup) : m_popup(popup) {}
91
92 protected:
93 void OnKillFocus(wxFocusEvent& event);
94 void OnChar(wxKeyEvent& event);
95
96 private:
97 wxPopupTransientWindow *m_popup;
98
99 DECLARE_EVENT_TABLE()
100 wxDECLARE_NO_COPY_CLASS(wxPopupFocusHandler);
101 };
102
103 // ----------------------------------------------------------------------------
104 // event tables
105 // ----------------------------------------------------------------------------
106
107 BEGIN_EVENT_TABLE(wxPopupWindowHandler, wxEvtHandler)
108 EVT_LEFT_DOWN(wxPopupWindowHandler::OnLeftDown)
109 EVT_MOUSE_CAPTURE_LOST(wxPopupWindowHandler::OnCaptureLost)
110 END_EVENT_TABLE()
111
112 BEGIN_EVENT_TABLE(wxPopupFocusHandler, wxEvtHandler)
113 EVT_KILL_FOCUS(wxPopupFocusHandler::OnKillFocus)
114 EVT_CHAR(wxPopupFocusHandler::OnChar)
115 END_EVENT_TABLE()
116
117 BEGIN_EVENT_TABLE(wxPopupTransientWindow, wxPopupWindow)
118 #if defined(__WXMSW__) || (defined(__WXMAC__) && wxOSX_USE_COCOA_OR_CARBON)
119 EVT_IDLE(wxPopupTransientWindow::OnIdle)
120 #endif
121 END_EVENT_TABLE()
122
123 // ============================================================================
124 // implementation
125 // ============================================================================
126
127 // ----------------------------------------------------------------------------
128 // wxPopupWindowBase
129 // ----------------------------------------------------------------------------
130
131 wxPopupWindowBase::~wxPopupWindowBase()
132 {
133 // this destructor is required for Darwin
134 }
135
136 bool wxPopupWindowBase::Create(wxWindow* WXUNUSED(parent), int WXUNUSED(flags))
137 {
138 return true;
139 }
140
141 void wxPopupWindowBase::Position(const wxPoint& ptOrigin,
142 const wxSize& size)
143 {
144 // determine the position and size of the screen we clamp the popup to
145 wxPoint posScreen;
146 wxSize sizeScreen;
147
148 const int displayNum = wxDisplay::GetFromPoint(ptOrigin);
149 if ( displayNum != wxNOT_FOUND )
150 {
151 const wxRect rectScreen = wxDisplay(displayNum).GetGeometry();
152 posScreen = rectScreen.GetPosition();
153 sizeScreen = rectScreen.GetSize();
154 }
155 else // outside of any display?
156 {
157 // just use the primary one then
158 posScreen = wxPoint(0, 0);
159 sizeScreen = wxGetDisplaySize();
160 }
161
162
163 const wxSize sizeSelf = GetSize();
164
165 // is there enough space to put the popup below the window (where we put it
166 // by default)?
167 wxCoord y = ptOrigin.y + size.y;
168 if ( y + sizeSelf.y > posScreen.y + sizeScreen.y )
169 {
170 // check if there is enough space above
171 if ( ptOrigin.y > sizeSelf.y )
172 {
173 // do position the control above the window
174 y -= size.y + sizeSelf.y;
175 }
176 //else: not enough space below nor above, leave below
177 }
178
179 // now check left/right too
180 wxCoord x = ptOrigin.x;
181
182 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
183 {
184 // shift the window to the left instead of the right.
185 x -= size.x;
186 x -= sizeSelf.x; // also shift it by window width.
187 }
188 else
189 x += size.x;
190
191
192 if ( x + sizeSelf.x > posScreen.x + sizeScreen.x )
193 {
194 // check if there is enough space to the left
195 if ( ptOrigin.x > sizeSelf.x )
196 {
197 // do position the control to the left
198 x -= size.x + sizeSelf.x;
199 }
200 //else: not enough space there neither, leave in default position
201 }
202
203 Move(x, y, wxSIZE_NO_ADJUSTMENTS);
204 }
205
206 // ----------------------------------------------------------------------------
207 // wxPopupTransientWindow
208 // ----------------------------------------------------------------------------
209
210 void wxPopupTransientWindow::Init()
211 {
212 m_child =
213 m_focus = NULL;
214
215 m_handlerFocus = NULL;
216 m_handlerPopup = NULL;
217 }
218
219 wxPopupTransientWindow::wxPopupTransientWindow(wxWindow *parent, int style)
220 {
221 Init();
222
223 (void)Create(parent, style);
224 }
225
226 wxPopupTransientWindow::~wxPopupTransientWindow()
227 {
228 if (m_handlerPopup && m_handlerPopup->GetNextHandler())
229 PopHandlers();
230
231 wxASSERT(!m_handlerFocus || !m_handlerFocus->GetNextHandler());
232 wxASSERT(!m_handlerPopup || !m_handlerPopup->GetNextHandler());
233
234 delete m_handlerFocus;
235 delete m_handlerPopup;
236 }
237
238 void wxPopupTransientWindow::PopHandlers()
239 {
240 if ( m_child )
241 {
242 if ( !m_child->RemoveEventHandler(m_handlerPopup) )
243 {
244 // something is very wrong and someone else probably deleted our
245 // handler - so don't risk deleting it second time
246 m_handlerPopup = NULL;
247 }
248 if (m_child->HasCapture())
249 {
250 m_child->ReleaseMouse();
251 }
252 m_child = NULL;
253 }
254
255 if ( m_focus )
256 {
257 if ( !m_focus->RemoveEventHandler(m_handlerFocus) )
258 {
259 // see above
260 m_handlerFocus = NULL;
261 }
262 }
263 m_focus = NULL;
264 }
265
266 void wxPopupTransientWindow::Popup(wxWindow *winFocus)
267 {
268 const wxWindowList& children = GetChildren();
269 if ( children.GetCount() )
270 {
271 m_child = children.GetFirst()->GetData();
272 }
273 else
274 {
275 m_child = this;
276 }
277
278 Show();
279
280 // There is a problem if these are still in use
281 wxASSERT(!m_handlerFocus || !m_handlerFocus->GetNextHandler());
282 wxASSERT(!m_handlerPopup || !m_handlerPopup->GetNextHandler());
283
284 if (!m_handlerPopup)
285 m_handlerPopup = new wxPopupWindowHandler(this);
286
287 m_child->PushEventHandler(m_handlerPopup);
288
289 #if defined(__WXMSW__)
290 // Focusing on child of popup window does not work on MSW unless WS_POPUP
291 // style is set. We do not even want to try to set the focus, as it may
292 // provoke errors on some Windows versions (Vista and later).
293 if ( ::GetWindowLong(GetHwnd(), GWL_STYLE) & WS_POPUP )
294 #endif
295 {
296 m_focus = winFocus ? winFocus : this;
297 m_focus->SetFocus();
298 }
299
300 #if defined( __WXMSW__ ) || (defined( __WXMAC__) && wxOSX_USE_COCOA_OR_CARBON)
301 // MSW doesn't allow to set focus to the popup window, but we need to
302 // subclass the window which has the focus, and not winFocus passed in or
303 // otherwise everything else breaks down
304 m_focus = FindFocus();
305 #elif defined(__WXGTK__)
306 // GTK+ catches the activate events from the popup
307 // window, not the focus events from the child window
308 m_focus = this;
309 #endif
310
311 if ( m_focus )
312 {
313 if (!m_handlerFocus)
314 m_handlerFocus = new wxPopupFocusHandler(this);
315
316 m_focus->PushEventHandler(m_handlerFocus);
317 }
318 }
319
320 bool wxPopupTransientWindow::Show( bool show )
321 {
322 #ifdef __WXGTK__
323 if (!show)
324 {
325 #ifdef __WXGTK3__
326 GdkDisplay* display = gtk_widget_get_display(m_widget);
327 GdkDeviceManager* manager = gdk_display_get_device_manager(display);
328 GdkDevice* device = gdk_device_manager_get_client_pointer(manager);
329 gdk_device_ungrab(device, unsigned(GDK_CURRENT_TIME));
330 #else
331 gdk_pointer_ungrab( (guint32)GDK_CURRENT_TIME );
332 #endif
333
334 gtk_grab_remove( m_widget );
335 }
336 #endif
337
338 #ifdef __WXX11__
339 if (!show)
340 {
341 XUngrabPointer( wxGlobalDisplay(), CurrentTime );
342 }
343 #endif
344
345 #if defined( __WXMSW__ ) || defined( __WXMAC__)
346 if (!show && m_child && m_child->HasCapture())
347 {
348 m_child->ReleaseMouse();
349 }
350 #endif
351
352 bool ret = wxPopupWindow::Show( show );
353
354 #ifdef __WXGTK__
355 if (show)
356 {
357 gtk_grab_add( m_widget );
358
359 const GdkEventMask mask = GdkEventMask(
360 GDK_BUTTON_PRESS_MASK |
361 GDK_BUTTON_RELEASE_MASK |
362 GDK_POINTER_MOTION_HINT_MASK |
363 GDK_POINTER_MOTION_MASK);
364 GdkWindow* window = gtk_widget_get_window(m_widget);
365 #ifdef __WXGTK3__
366 GdkDisplay* display = gdk_window_get_display(window);
367 GdkDeviceManager* manager = gdk_display_get_device_manager(display);
368 GdkDevice* device = gdk_device_manager_get_client_pointer(manager);
369 gdk_device_grab(device, window,
370 GDK_OWNERSHIP_NONE, false, mask, NULL, unsigned(GDK_CURRENT_TIME));
371 #else
372 gdk_pointer_grab( window, true,
373 mask,
374 NULL,
375 NULL,
376 (guint32)GDK_CURRENT_TIME );
377 #endif
378 }
379 #endif
380
381 #ifdef __WXX11__
382 if (show)
383 {
384 Window xwindow = (Window) m_clientWindow;
385
386 /* int res =*/ XGrabPointer(wxGlobalDisplay(), xwindow,
387 True,
388 ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask,
389 GrabModeAsync,
390 GrabModeAsync,
391 None,
392 None,
393 CurrentTime );
394 }
395 #endif
396
397 #if defined( __WXMSW__ ) || defined( __WXMAC__)
398 if (show && m_child)
399 {
400 // Assume that the mouse is outside the popup to begin with
401 m_child->CaptureMouse();
402 }
403 #endif
404
405 return ret;
406 }
407
408 bool wxPopupTransientWindow::Destroy()
409 {
410 // The popup window can be deleted at any moment, even while some events
411 // are still being processed for it, so delay its real destruction until
412 // the next idle time when we're sure that it's safe to really destroy it.
413
414 wxCHECK_MSG( !wxPendingDelete.Member(this), false,
415 wxS("Shouldn't destroy the popup twice.") );
416
417 wxPendingDelete.Append(this);
418
419 return true;
420 }
421
422 void wxPopupTransientWindow::Dismiss()
423 {
424 Hide();
425 PopHandlers();
426 }
427
428 void wxPopupTransientWindow::DismissAndNotify()
429 {
430 Dismiss();
431 OnDismiss();
432 }
433
434 void wxPopupTransientWindow::OnDismiss()
435 {
436 // nothing to do here - but it may be interesting for derived class
437 }
438
439 bool wxPopupTransientWindow::ProcessLeftDown(wxMouseEvent& WXUNUSED(event))
440 {
441 // no special processing here
442 return false;
443 }
444
445 #if defined(__WXMSW__) ||(defined(__WXMAC__) && wxOSX_USE_COCOA_OR_CARBON)
446 void wxPopupTransientWindow::OnIdle(wxIdleEvent& event)
447 {
448 event.Skip();
449
450 if (IsShown() && m_child)
451 {
452 // Store the last mouse position to minimize the number of calls to
453 // wxFindWindowAtPoint() which are quite expensive.
454 static wxPoint s_posLast;
455 const wxPoint pos = wxGetMousePosition();
456 if ( pos != s_posLast )
457 {
458 s_posLast = pos;
459
460 wxWindow* const winUnderMouse = wxFindWindowAtPoint(pos);
461
462 // We release the mouse capture while the mouse is inside the popup
463 // itself to allow using it normally with the controls inside it.
464 if ( wxGetTopLevelParent(winUnderMouse) == this )
465 {
466 if ( m_child->HasCapture() )
467 {
468 m_child->ReleaseMouse();
469 }
470 }
471 else // And we reacquire it as soon as the mouse goes outside.
472 {
473 if ( !m_child->HasCapture() )
474 {
475 m_child->CaptureMouse();
476 }
477 }
478 }
479 }
480 }
481 #endif // wxOSX/Carbon
482
483
484 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
485
486 // ----------------------------------------------------------------------------
487 // wxPopupComboWindow
488 // ----------------------------------------------------------------------------
489
490 BEGIN_EVENT_TABLE(wxPopupComboWindow, wxPopupTransientWindow)
491 EVT_KEY_DOWN(wxPopupComboWindow::OnKeyDown)
492 END_EVENT_TABLE()
493
494 wxPopupComboWindow::wxPopupComboWindow(wxComboCtrl *parent)
495 : wxPopupTransientWindow(parent)
496 {
497 m_combo = parent;
498 }
499
500 bool wxPopupComboWindow::Create(wxComboCtrl *parent)
501 {
502 m_combo = parent;
503
504 return wxPopupWindow::Create(parent);
505 }
506
507 void wxPopupComboWindow::PositionNearCombo()
508 {
509 // the origin point must be in screen coords
510 wxPoint ptOrigin = m_combo->ClientToScreen(wxPoint(0,0));
511
512 #if 0 //def __WXUNIVERSAL__
513 // account for the fact that (0, 0) is not the top left corner of the
514 // window: there is also the border
515 wxRect rectBorders = m_combo->GetRenderer()->
516 GetBorderDimensions(m_combo->GetBorder());
517 ptOrigin.x -= rectBorders.x;
518 ptOrigin.y -= rectBorders.y;
519 #endif // __WXUNIVERSAL__
520
521 // position below or above the combobox: the width is 0 to put it exactly
522 // below us, not to the left or to the right
523 Position(ptOrigin, wxSize(0, m_combo->GetSize().y));
524 }
525
526 void wxPopupComboWindow::OnDismiss()
527 {
528 m_combo->OnPopupDismiss(true);
529 }
530
531 void wxPopupComboWindow::OnKeyDown(wxKeyEvent& event)
532 {
533 m_combo->ProcessWindowEvent(event);
534 }
535
536 #endif // wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
537
538 // ----------------------------------------------------------------------------
539 // wxPopupWindowHandler
540 // ----------------------------------------------------------------------------
541
542 void wxPopupWindowHandler::OnLeftDown(wxMouseEvent& event)
543 {
544 // let the window have it first (we're the first event handler in the chain
545 // of handlers for this window)
546 if ( m_popup->ProcessLeftDown(event) )
547 {
548 return;
549 }
550
551 wxPoint pos = event.GetPosition();
552
553 // in non-Univ ports the system manages scrollbars for us
554 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
555 // scrollbar on which the click occurred
556 wxWindow *sbar = NULL;
557 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
558
559 wxWindow *win = (wxWindow *)event.GetEventObject();
560
561 switch ( win->HitTest(pos.x, pos.y) )
562 {
563 case wxHT_WINDOW_OUTSIDE:
564 {
565 // do the coords translation now as after DismissAndNotify()
566 // m_popup may be destroyed
567 wxMouseEvent event2(event);
568
569 m_popup->ClientToScreen(&event2.m_x, &event2.m_y);
570
571 // clicking outside a popup dismisses it
572 m_popup->DismissAndNotify();
573
574 // dismissing a tooltip shouldn't waste a click, i.e. you
575 // should be able to dismiss it and press the button with the
576 // same click, so repost this event to the window beneath us
577 wxWindow *winUnder = wxFindWindowAtPoint(event2.GetPosition());
578 if ( winUnder )
579 {
580 // translate the event coords to the ones of the window
581 // which is going to get the event
582 winUnder->ScreenToClient(&event2.m_x, &event2.m_y);
583
584 event2.SetEventObject(winUnder);
585 wxPostEvent(winUnder->GetEventHandler(), event2);
586 }
587 }
588 break;
589
590 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
591 case wxHT_WINDOW_HORZ_SCROLLBAR:
592 sbar = win->GetScrollbar(wxHORIZONTAL);
593 break;
594
595 case wxHT_WINDOW_VERT_SCROLLBAR:
596 sbar = win->GetScrollbar(wxVERTICAL);
597 break;
598 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
599
600 default:
601 // forgot to update the switch after adding a new hit test code?
602 wxFAIL_MSG( wxT("unexpected HitTest() return value") );
603 // fall through
604
605 case wxHT_WINDOW_CORNER:
606 // don't actually know if this one is good for anything, but let it
607 // pass just in case
608
609 case wxHT_WINDOW_INSIDE:
610 // let the normal processing take place
611 event.Skip();
612 break;
613 }
614
615 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
616 if ( sbar )
617 {
618 // translate the event coordinates to the scrollbar ones
619 pos = sbar->ScreenToClient(win->ClientToScreen(pos));
620
621 // and give the event to it
622 wxMouseEvent event2 = event;
623 event2.m_x = pos.x;
624 event2.m_y = pos.y;
625
626 (void)sbar->GetEventHandler()->ProcessEvent(event2);
627 }
628 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
629 }
630
631 void
632 wxPopupWindowHandler::OnCaptureLost(wxMouseCaptureLostEvent& WXUNUSED(event))
633 {
634 m_popup->DismissAndNotify();
635
636 // There is no need to skip the event here, normally we've already dealt
637 // with the focus loss.
638 }
639
640 // ----------------------------------------------------------------------------
641 // wxPopupFocusHandler
642 // ----------------------------------------------------------------------------
643
644 void wxPopupFocusHandler::OnKillFocus(wxFocusEvent& event)
645 {
646 // when we lose focus we always disappear - unless it goes to the popup (in
647 // which case we don't really lose it)
648 wxWindow *win = event.GetWindow();
649 while ( win )
650 {
651 if ( win == m_popup )
652 return;
653 win = win->GetParent();
654 }
655
656 m_popup->DismissAndNotify();
657 }
658
659 void wxPopupFocusHandler::OnChar(wxKeyEvent& event)
660 {
661 // we can be associated with the popup itself in which case we should avoid
662 // infinite recursion
663 static int s_inside;
664 wxRecursionGuard guard(s_inside);
665 if ( guard.IsInside() )
666 {
667 event.Skip();
668 return;
669 }
670
671 // let the window have it first, it might process the keys
672 if ( !m_popup->GetEventHandler()->ProcessEvent(event) )
673 {
674 // by default, dismiss the popup
675 m_popup->DismissAndNotify();
676 }
677 }
678
679 #endif // wxUSE_POPUPWIN