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:
wxPopupWindowHandler(wxPopupTransientWindow * popup)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:
wxPopupFocusHandler(wxPopupTransientWindow * popup)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 
BEGIN_EVENT_TABLE(wxPopupWindowHandler,wxEvtHandler)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 
Create(wxWindow * WXUNUSED (parent),int WXUNUSED (flags))136 bool wxPopupWindowBase::Create(wxWindow* WXUNUSED(parent), int WXUNUSED(flags))
137 {
138     return true;
139 }
140 
Position(const wxPoint & ptOrigin,const wxSize & size)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 
Init()210 void wxPopupTransientWindow::Init()
211 {
212     m_child =
213     m_focus = NULL;
214 
215     m_handlerFocus = NULL;
216     m_handlerPopup = NULL;
217 }
218 
wxPopupTransientWindow(wxWindow * parent,int style)219 wxPopupTransientWindow::wxPopupTransientWindow(wxWindow *parent, int style)
220 {
221     Init();
222 
223     (void)Create(parent, style);
224 }
225 
~wxPopupTransientWindow()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 
PopHandlers()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 
Popup(wxWindow * winFocus)266 void wxPopupTransientWindow::Popup(wxWindow *winFocus)
267 {
268     // If we have a single child, we suppose that it must cover the entire
269     // popup window and hence we give the mouse capture to it instead of
270     // keeping it for ourselves.
271     //
272     // Notice that this works best for combobox-like popups which have a single
273     // control inside them and not so well for popups containing a single
274     // wxPanel with multiple children inside it but OTOH it does no harm in
275     // this case neither and we can't reliably distinguish between them.
276     const wxWindowList& children = GetChildren();
277     if ( children.GetCount() == 1 )
278     {
279         m_child = children.GetFirst()->GetData();
280     }
281     else
282     {
283         m_child = this;
284     }
285 
286     Show();
287 
288     // There is a problem if these are still in use
289     wxASSERT(!m_handlerFocus || !m_handlerFocus->GetNextHandler());
290     wxASSERT(!m_handlerPopup || !m_handlerPopup->GetNextHandler());
291 
292     if (!m_handlerPopup)
293         m_handlerPopup = new wxPopupWindowHandler(this);
294 
295     m_child->PushEventHandler(m_handlerPopup);
296 
297 #if defined(__WXMSW__)
298     // Focusing on child of popup window does not work on MSW unless WS_POPUP
299     // style is set. We do not even want to try to set the focus, as it may
300     // provoke errors on some Windows versions (Vista and later).
301     if ( ::GetWindowLong(GetHwnd(), GWL_STYLE) & WS_POPUP )
302 #endif
303     {
304         m_focus = winFocus ? winFocus : this;
305         m_focus->SetFocus();
306     }
307 
308 #if defined( __WXMSW__ ) || (defined( __WXMAC__) && wxOSX_USE_COCOA_OR_CARBON)
309     // MSW doesn't allow to set focus to the popup window, but we need to
310     // subclass the window which has the focus, and not winFocus passed in or
311     // otherwise everything else breaks down
312     m_focus = FindFocus();
313 #elif defined(__WXGTK__)
314     // GTK+ catches the activate events from the popup
315     // window, not the focus events from the child window
316     m_focus = this;
317 #endif
318 
319     if ( m_focus )
320     {
321         if (!m_handlerFocus)
322             m_handlerFocus = new wxPopupFocusHandler(this);
323 
324         m_focus->PushEventHandler(m_handlerFocus);
325     }
326 }
327 
Show(bool show)328 bool wxPopupTransientWindow::Show( bool show )
329 {
330 #ifdef __WXGTK__
331     if (!show)
332     {
333 #ifdef __WXGTK3__
334         GdkDisplay* display = gtk_widget_get_display(m_widget);
335         GdkDeviceManager* manager = gdk_display_get_device_manager(display);
336         GdkDevice* device = gdk_device_manager_get_client_pointer(manager);
337         gdk_device_ungrab(device, unsigned(GDK_CURRENT_TIME));
338 #else
339         gdk_pointer_ungrab( (guint32)GDK_CURRENT_TIME );
340 #endif
341 
342         gtk_grab_remove( m_widget );
343     }
344 #endif
345 
346 #ifdef __WXX11__
347     if (!show)
348     {
349         XUngrabPointer( wxGlobalDisplay(), CurrentTime );
350     }
351 #endif
352 
353 #if defined( __WXMSW__ ) || defined( __WXMAC__)
354     if (!show && m_child && m_child->HasCapture())
355     {
356         m_child->ReleaseMouse();
357     }
358 #endif
359 
360     bool ret = wxPopupWindow::Show( show );
361 
362 #ifdef __WXGTK__
363     if (show)
364     {
365         gtk_grab_add( m_widget );
366 
367         const GdkEventMask mask = GdkEventMask(
368             GDK_BUTTON_PRESS_MASK |
369             GDK_BUTTON_RELEASE_MASK |
370             GDK_POINTER_MOTION_HINT_MASK |
371             GDK_POINTER_MOTION_MASK);
372         GdkWindow* window = gtk_widget_get_window(m_widget);
373 #ifdef __WXGTK3__
374         GdkDisplay* display = gdk_window_get_display(window);
375         GdkDeviceManager* manager = gdk_display_get_device_manager(display);
376         GdkDevice* device = gdk_device_manager_get_client_pointer(manager);
377         gdk_device_grab(device, window,
378             GDK_OWNERSHIP_NONE, true, mask, NULL, unsigned(GDK_CURRENT_TIME));
379 #else
380         gdk_pointer_grab( window, true,
381                           mask,
382                           NULL,
383                           NULL,
384                           (guint32)GDK_CURRENT_TIME );
385 #endif
386     }
387 #endif
388 
389 #ifdef __WXX11__
390     if (show)
391     {
392         Window xwindow = (Window) m_clientWindow;
393 
394         /* int res =*/ XGrabPointer(wxGlobalDisplay(), xwindow,
395             True,
396             ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask,
397             GrabModeAsync,
398             GrabModeAsync,
399             None,
400             None,
401             CurrentTime );
402     }
403 #endif
404 
405 #if defined( __WXMSW__ ) || defined( __WXMAC__)
406     if (show && m_child)
407     {
408         // Assume that the mouse is outside the popup to begin with
409         m_child->CaptureMouse();
410     }
411 #endif
412 
413     return ret;
414 }
415 
Destroy()416 bool wxPopupTransientWindow::Destroy()
417 {
418     // The popup window can be deleted at any moment, even while some events
419     // are still being processed for it, so delay its real destruction until
420     // the next idle time when we're sure that it's safe to really destroy it.
421 
422     wxCHECK_MSG( !wxPendingDelete.Member(this), false,
423                  wxS("Shouldn't destroy the popup twice.") );
424 
425     wxPendingDelete.Append(this);
426 
427     return true;
428 }
429 
Dismiss()430 void wxPopupTransientWindow::Dismiss()
431 {
432     Hide();
433     PopHandlers();
434 }
435 
DismissAndNotify()436 void wxPopupTransientWindow::DismissAndNotify()
437 {
438     Dismiss();
439     OnDismiss();
440 }
441 
OnDismiss()442 void wxPopupTransientWindow::OnDismiss()
443 {
444     // nothing to do here - but it may be interesting for derived class
445 }
446 
ProcessLeftDown(wxMouseEvent & WXUNUSED (event))447 bool wxPopupTransientWindow::ProcessLeftDown(wxMouseEvent& WXUNUSED(event))
448 {
449     // no special processing here
450     return false;
451 }
452 
453 #if defined(__WXMSW__) ||(defined(__WXMAC__) && wxOSX_USE_COCOA_OR_CARBON)
OnIdle(wxIdleEvent & event)454 void wxPopupTransientWindow::OnIdle(wxIdleEvent& event)
455 {
456     event.Skip();
457 
458     if (IsShown() && m_child)
459     {
460         // Store the last mouse position to minimize the number of calls to
461         // wxFindWindowAtPoint() which are quite expensive.
462         static wxPoint s_posLast;
463         const wxPoint pos = wxGetMousePosition();
464         if ( pos != s_posLast )
465         {
466             s_posLast = pos;
467 
468             wxWindow* const winUnderMouse = wxFindWindowAtPoint(pos);
469 
470             // We release the mouse capture while the mouse is inside the popup
471             // itself to allow using it normally with the controls inside it.
472             if ( wxGetTopLevelParent(winUnderMouse) == this )
473             {
474                 if ( m_child->HasCapture() )
475                 {
476                     m_child->ReleaseMouse();
477                 }
478             }
479             else // And we reacquire it as soon as the mouse goes outside.
480             {
481                 if ( !m_child->HasCapture() )
482                 {
483                     m_child->CaptureMouse();
484                 }
485             }
486         }
487     }
488 }
489 #endif // wxOSX/Carbon
490 
491 
492 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
493 
494 // ----------------------------------------------------------------------------
495 // wxPopupComboWindow
496 // ----------------------------------------------------------------------------
497 
BEGIN_EVENT_TABLE(wxPopupComboWindow,wxPopupTransientWindow)498 BEGIN_EVENT_TABLE(wxPopupComboWindow, wxPopupTransientWindow)
499     EVT_KEY_DOWN(wxPopupComboWindow::OnKeyDown)
500 END_EVENT_TABLE()
501 
502 wxPopupComboWindow::wxPopupComboWindow(wxComboCtrl *parent)
503                   : wxPopupTransientWindow(parent)
504 {
505     m_combo = parent;
506 }
507 
Create(wxComboCtrl * parent)508 bool wxPopupComboWindow::Create(wxComboCtrl *parent)
509 {
510     m_combo = parent;
511 
512     return wxPopupWindow::Create(parent);
513 }
514 
PositionNearCombo()515 void wxPopupComboWindow::PositionNearCombo()
516 {
517     // the origin point must be in screen coords
518     wxPoint ptOrigin = m_combo->ClientToScreen(wxPoint(0,0));
519 
520 #if 0 //def __WXUNIVERSAL__
521     // account for the fact that (0, 0) is not the top left corner of the
522     // window: there is also the border
523     wxRect rectBorders = m_combo->GetRenderer()->
524                             GetBorderDimensions(m_combo->GetBorder());
525     ptOrigin.x -= rectBorders.x;
526     ptOrigin.y -= rectBorders.y;
527 #endif // __WXUNIVERSAL__
528 
529     // position below or above the combobox: the width is 0 to put it exactly
530     // below us, not to the left or to the right
531     Position(ptOrigin, wxSize(0, m_combo->GetSize().y));
532 }
533 
OnDismiss()534 void wxPopupComboWindow::OnDismiss()
535 {
536     m_combo->OnPopupDismiss(true);
537 }
538 
OnKeyDown(wxKeyEvent & event)539 void wxPopupComboWindow::OnKeyDown(wxKeyEvent& event)
540 {
541     m_combo->ProcessWindowEvent(event);
542 }
543 
544 #endif // wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
545 
546 // ----------------------------------------------------------------------------
547 // wxPopupWindowHandler
548 // ----------------------------------------------------------------------------
549 
OnLeftDown(wxMouseEvent & event)550 void wxPopupWindowHandler::OnLeftDown(wxMouseEvent& event)
551 {
552     // let the window have it first (we're the first event handler in the chain
553     // of handlers for this window)
554     if ( m_popup->ProcessLeftDown(event) )
555     {
556         return;
557     }
558 
559     wxPoint pos = event.GetPosition();
560 
561     // in non-Univ ports the system manages scrollbars for us
562 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
563     // scrollbar on which the click occurred
564     wxWindow *sbar = NULL;
565 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
566 
567     wxWindow *win = (wxWindow *)event.GetEventObject();
568 
569     switch ( win->HitTest(pos.x, pos.y) )
570     {
571         case wxHT_WINDOW_OUTSIDE:
572             {
573                 // do the coords translation now as after DismissAndNotify()
574                 // m_popup may be destroyed
575                 wxMouseEvent event2(event);
576 
577                 m_popup->ClientToScreen(&event2.m_x, &event2.m_y);
578 
579                 // clicking outside a popup dismisses it
580                 m_popup->DismissAndNotify();
581 
582                 // dismissing a tooltip shouldn't waste a click, i.e. you
583                 // should be able to dismiss it and press the button with the
584                 // same click, so repost this event to the window beneath us
585                 wxWindow *winUnder = wxFindWindowAtPoint(event2.GetPosition());
586                 if ( winUnder )
587                 {
588                     // translate the event coords to the ones of the window
589                     // which is going to get the event
590                     winUnder->ScreenToClient(&event2.m_x, &event2.m_y);
591 
592                     event2.SetEventObject(winUnder);
593                     wxPostEvent(winUnder->GetEventHandler(), event2);
594                 }
595             }
596             break;
597 
598 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
599         case wxHT_WINDOW_HORZ_SCROLLBAR:
600             sbar = win->GetScrollbar(wxHORIZONTAL);
601             break;
602 
603         case wxHT_WINDOW_VERT_SCROLLBAR:
604             sbar = win->GetScrollbar(wxVERTICAL);
605             break;
606 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
607 
608         default:
609             // forgot to update the switch after adding a new hit test code?
610             wxFAIL_MSG( wxT("unexpected HitTest() return value") );
611             // fall through
612 
613         case wxHT_WINDOW_CORNER:
614             // don't actually know if this one is good for anything, but let it
615             // pass just in case
616 
617         case wxHT_WINDOW_INSIDE:
618             // let the normal processing take place
619             event.Skip();
620             break;
621     }
622 
623 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
624     if ( sbar )
625     {
626         // translate the event coordinates to the scrollbar ones
627         pos = sbar->ScreenToClient(win->ClientToScreen(pos));
628 
629         // and give the event to it
630         wxMouseEvent event2 = event;
631         event2.m_x = pos.x;
632         event2.m_y = pos.y;
633 
634         (void)sbar->GetEventHandler()->ProcessEvent(event2);
635     }
636 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
637 }
638 
639 void
OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED (event))640 wxPopupWindowHandler::OnCaptureLost(wxMouseCaptureLostEvent& WXUNUSED(event))
641 {
642     m_popup->DismissAndNotify();
643 
644     // There is no need to skip the event here, normally we've already dealt
645     // with the focus loss.
646 }
647 
648 // ----------------------------------------------------------------------------
649 // wxPopupFocusHandler
650 // ----------------------------------------------------------------------------
651 
OnKillFocus(wxFocusEvent & event)652 void wxPopupFocusHandler::OnKillFocus(wxFocusEvent& event)
653 {
654     // when we lose focus we always disappear - unless it goes to the popup (in
655     // which case we don't really lose it)
656     wxWindow *win = event.GetWindow();
657     while ( win )
658     {
659         if ( win == m_popup )
660             return;
661         win = win->GetParent();
662     }
663 
664     m_popup->DismissAndNotify();
665 }
666 
OnChar(wxKeyEvent & event)667 void wxPopupFocusHandler::OnChar(wxKeyEvent& event)
668 {
669     // we can be associated with the popup itself in which case we should avoid
670     // infinite recursion
671     static int s_inside;
672     wxRecursionGuard guard(s_inside);
673     if ( guard.IsInside() )
674     {
675         event.Skip();
676         return;
677     }
678 
679     // let the window have it first, it might process the keys
680     if ( !m_popup->GetEventHandler()->ProcessEvent(event) )
681     {
682         // by default, dismiss the popup
683         m_popup->DismissAndNotify();
684     }
685 }
686 
687 #endif // wxUSE_POPUPWIN
688