1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/popupcmn.cpp
3 // Purpose: implementation of wxPopupTransientWindow
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 2001 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
29 #include "wx/popupwin.h"
32 #include "wx/combobox.h" // wxComboCtrl
33 #include "wx/app.h" // wxPostEvent
37 #include "wx/display.h"
38 #include "wx/recguard.h"
40 #ifdef __WXUNIVERSAL__
41 #include "wx/univ/renderer.h"
42 #include "wx/scrolbar.h"
43 #endif // __WXUNIVERSAL__
47 #if GTK_CHECK_VERSION(2,0,0)
48 #include "wx/gtk/private/gtk2-compat.h"
50 #define gtk_widget_get_window(x) x->window
52 #elif defined(__WXMSW__)
53 #include "wx/msw/private.h"
54 #elif defined(__WXX11__)
55 #include "wx/x11/private.h"
58 IMPLEMENT_DYNAMIC_CLASS(wxPopupWindow
, wxWindow
)
59 IMPLEMENT_DYNAMIC_CLASS(wxPopupTransientWindow
, wxPopupWindow
)
61 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
62 IMPLEMENT_DYNAMIC_CLASS(wxPopupComboWindow
, wxPopupTransientWindow
)
65 // ----------------------------------------------------------------------------
67 // ----------------------------------------------------------------------------
69 // event handlers which we use to intercept events which cause the popup to
71 class wxPopupWindowHandler
: public wxEvtHandler
74 wxPopupWindowHandler(wxPopupTransientWindow
*popup
) : m_popup(popup
) {}
78 void OnLeftDown(wxMouseEvent
& event
);
79 void OnCaptureLost(wxMouseCaptureLostEvent
& event
);
82 wxPopupTransientWindow
*m_popup
;
85 wxDECLARE_NO_COPY_CLASS(wxPopupWindowHandler
);
88 class wxPopupFocusHandler
: public wxEvtHandler
91 wxPopupFocusHandler(wxPopupTransientWindow
*popup
) : m_popup(popup
) {}
94 void OnKillFocus(wxFocusEvent
& event
);
95 void OnChar(wxKeyEvent
& event
);
98 wxPopupTransientWindow
*m_popup
;
100 DECLARE_EVENT_TABLE()
101 wxDECLARE_NO_COPY_CLASS(wxPopupFocusHandler
);
104 // ----------------------------------------------------------------------------
106 // ----------------------------------------------------------------------------
108 BEGIN_EVENT_TABLE(wxPopupWindowHandler
, wxEvtHandler
)
109 EVT_LEFT_DOWN(wxPopupWindowHandler::OnLeftDown
)
110 EVT_MOUSE_CAPTURE_LOST(wxPopupWindowHandler::OnCaptureLost
)
113 BEGIN_EVENT_TABLE(wxPopupFocusHandler
, wxEvtHandler
)
114 EVT_KILL_FOCUS(wxPopupFocusHandler::OnKillFocus
)
115 EVT_CHAR(wxPopupFocusHandler::OnChar
)
118 BEGIN_EVENT_TABLE(wxPopupTransientWindow
, wxPopupWindow
)
119 #if defined(__WXMSW__) || (defined(__WXMAC__) && wxOSX_USE_COCOA_OR_CARBON)
120 EVT_IDLE(wxPopupTransientWindow::OnIdle
)
124 // ============================================================================
126 // ============================================================================
128 // ----------------------------------------------------------------------------
130 // ----------------------------------------------------------------------------
132 wxPopupWindowBase::~wxPopupWindowBase()
134 // this destructor is required for Darwin
137 bool wxPopupWindowBase::Create(wxWindow
* WXUNUSED(parent
), int WXUNUSED(flags
))
142 void wxPopupWindowBase::Position(const wxPoint
& ptOrigin
,
145 // determine the position and size of the screen we clamp the popup to
149 const int displayNum
= wxDisplay::GetFromPoint(ptOrigin
);
150 if ( displayNum
!= wxNOT_FOUND
)
152 const wxRect rectScreen
= wxDisplay(displayNum
).GetGeometry();
153 posScreen
= rectScreen
.GetPosition();
154 sizeScreen
= rectScreen
.GetSize();
156 else // outside of any display?
158 // just use the primary one then
159 posScreen
= wxPoint(0, 0);
160 sizeScreen
= wxGetDisplaySize();
164 const wxSize sizeSelf
= GetSize();
166 // is there enough space to put the popup below the window (where we put it
168 wxCoord y
= ptOrigin
.y
+ size
.y
;
169 if ( y
+ sizeSelf
.y
> posScreen
.y
+ sizeScreen
.y
)
171 // check if there is enough space above
172 if ( ptOrigin
.y
> sizeSelf
.y
)
174 // do position the control above the window
175 y
-= size
.y
+ sizeSelf
.y
;
177 //else: not enough space below nor above, leave below
180 // now check left/right too
181 wxCoord x
= ptOrigin
.x
;
183 if ( wxTheApp
->GetLayoutDirection() == wxLayout_RightToLeft
)
185 // shift the window to the left instead of the right.
187 x
-= sizeSelf
.x
; // also shift it by window width.
193 if ( x
+ sizeSelf
.x
> posScreen
.x
+ sizeScreen
.x
)
195 // check if there is enough space to the left
196 if ( ptOrigin
.x
> sizeSelf
.x
)
198 // do position the control to the left
199 x
-= size
.x
+ sizeSelf
.x
;
201 //else: not enough space there neither, leave in default position
204 Move(x
, y
, wxSIZE_NO_ADJUSTMENTS
);
207 // ----------------------------------------------------------------------------
208 // wxPopupTransientWindow
209 // ----------------------------------------------------------------------------
211 void wxPopupTransientWindow::Init()
216 m_handlerFocus
= NULL
;
217 m_handlerPopup
= NULL
;
220 wxPopupTransientWindow::wxPopupTransientWindow(wxWindow
*parent
, int style
)
224 (void)Create(parent
, style
);
227 wxPopupTransientWindow::~wxPopupTransientWindow()
229 if (m_handlerPopup
&& m_handlerPopup
->GetNextHandler())
232 wxASSERT(!m_handlerFocus
|| !m_handlerFocus
->GetNextHandler());
233 wxASSERT(!m_handlerPopup
|| !m_handlerPopup
->GetNextHandler());
235 delete m_handlerFocus
;
236 delete m_handlerPopup
;
239 void wxPopupTransientWindow::PopHandlers()
243 if ( !m_child
->RemoveEventHandler(m_handlerPopup
) )
245 // something is very wrong and someone else probably deleted our
246 // handler - so don't risk deleting it second time
247 m_handlerPopup
= NULL
;
249 if (m_child
->HasCapture())
251 m_child
->ReleaseMouse();
258 if ( !m_focus
->RemoveEventHandler(m_handlerFocus
) )
261 m_handlerFocus
= NULL
;
267 void wxPopupTransientWindow::Popup(wxWindow
*winFocus
)
269 const wxWindowList
& children
= GetChildren();
270 if ( children
.GetCount() )
272 m_child
= children
.GetFirst()->GetData();
281 // There is a problem if these are still in use
282 wxASSERT(!m_handlerFocus
|| !m_handlerFocus
->GetNextHandler());
283 wxASSERT(!m_handlerPopup
|| !m_handlerPopup
->GetNextHandler());
286 m_handlerPopup
= new wxPopupWindowHandler(this);
288 m_child
->PushEventHandler(m_handlerPopup
);
290 #if defined(__WXMSW__)
291 // Focusing on child of popup window does not work on MSW unless WS_POPUP
292 // style is set. We do not even want to try to set the focus, as it may
293 // provoke errors on some Windows versions (Vista and later).
294 if ( ::GetWindowLong(GetHwnd(), GWL_STYLE
) & WS_POPUP
)
297 m_focus
= winFocus
? winFocus
: this;
301 #if defined( __WXMSW__ ) || (defined( __WXMAC__) && wxOSX_USE_COCOA_OR_CARBON)
302 // MSW doesn't allow to set focus to the popup window, but we need to
303 // subclass the window which has the focus, and not winFocus passed in or
304 // otherwise everything else breaks down
305 m_focus
= FindFocus();
306 #elif defined(__WXGTK__)
307 // GTK+ catches the activate events from the popup
308 // window, not the focus events from the child window
315 m_handlerFocus
= new wxPopupFocusHandler(this);
317 m_focus
->PushEventHandler(m_handlerFocus
);
321 bool wxPopupTransientWindow::Show( bool show
)
327 GdkDisplay
* display
= gtk_widget_get_display(m_widget
);
328 GdkDeviceManager
* manager
= gdk_display_get_device_manager(display
);
329 GdkDevice
* device
= gdk_device_manager_get_client_pointer(manager
);
330 gdk_device_ungrab(device
, unsigned(GDK_CURRENT_TIME
));
332 gdk_pointer_ungrab( (guint32
)GDK_CURRENT_TIME
);
335 gtk_grab_remove( m_widget
);
342 XUngrabPointer( wxGlobalDisplay(), CurrentTime
);
346 #if defined( __WXMSW__ ) || defined( __WXMAC__)
347 if (!show
&& m_child
&& m_child
->HasCapture())
349 m_child
->ReleaseMouse();
353 bool ret
= wxPopupWindow::Show( show
);
358 gtk_grab_add( m_widget
);
360 const GdkEventMask mask
= GdkEventMask(
361 GDK_BUTTON_PRESS_MASK
|
362 GDK_BUTTON_RELEASE_MASK
|
363 GDK_POINTER_MOTION_HINT_MASK
|
364 GDK_POINTER_MOTION_MASK
);
365 GdkWindow
* window
= gtk_widget_get_window(m_widget
);
367 GdkDisplay
* display
= gdk_window_get_display(window
);
368 GdkDeviceManager
* manager
= gdk_display_get_device_manager(display
);
369 GdkDevice
* device
= gdk_device_manager_get_client_pointer(manager
);
370 gdk_device_grab(device
, window
,
371 GDK_OWNERSHIP_NONE
, false, mask
, NULL
, unsigned(GDK_CURRENT_TIME
));
373 gdk_pointer_grab( window
, true,
377 (guint32
)GDK_CURRENT_TIME
);
385 Window xwindow
= (Window
) m_clientWindow
;
387 /* int res =*/ XGrabPointer(wxGlobalDisplay(), xwindow
,
389 ButtonPressMask
| ButtonReleaseMask
| ButtonMotionMask
| EnterWindowMask
| LeaveWindowMask
| PointerMotionMask
,
398 #if defined( __WXMSW__ ) || defined( __WXMAC__)
401 // Assume that the mouse is outside the popup to begin with
402 m_child
->CaptureMouse();
409 bool wxPopupTransientWindow::Destroy()
411 // The popup window can be deleted at any moment, even while some events
412 // are still being processed for it, so delay its real destruction until
413 // the next idle time when we're sure that it's safe to really destroy it.
415 wxCHECK_MSG( !wxPendingDelete
.Member(this), false,
416 wxS("Shouldn't destroy the popup twice.") );
418 wxPendingDelete
.Append(this);
423 void wxPopupTransientWindow::Dismiss()
429 void wxPopupTransientWindow::DismissAndNotify()
435 void wxPopupTransientWindow::OnDismiss()
437 // nothing to do here - but it may be interesting for derived class
440 bool wxPopupTransientWindow::ProcessLeftDown(wxMouseEvent
& WXUNUSED(event
))
442 // no special processing here
446 #if defined(__WXMSW__) ||(defined(__WXMAC__) && wxOSX_USE_COCOA_OR_CARBON)
447 void wxPopupTransientWindow::OnIdle(wxIdleEvent
& event
)
451 if (IsShown() && m_child
)
453 wxPoint pos
= ScreenToClient(wxGetMousePosition());
454 wxRect
rect(GetSize());
456 if ( rect
.Contains(pos
) )
458 if ( m_child
->HasCapture() )
460 m_child
->ReleaseMouse();
465 if ( !m_child
->HasCapture() )
467 m_child
->CaptureMouse();
472 #endif // wxOSX/Carbon
475 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
477 // ----------------------------------------------------------------------------
478 // wxPopupComboWindow
479 // ----------------------------------------------------------------------------
481 BEGIN_EVENT_TABLE(wxPopupComboWindow
, wxPopupTransientWindow
)
482 EVT_KEY_DOWN(wxPopupComboWindow::OnKeyDown
)
485 wxPopupComboWindow::wxPopupComboWindow(wxComboCtrl
*parent
)
486 : wxPopupTransientWindow(parent
)
491 bool wxPopupComboWindow::Create(wxComboCtrl
*parent
)
495 return wxPopupWindow::Create(parent
);
498 void wxPopupComboWindow::PositionNearCombo()
500 // the origin point must be in screen coords
501 wxPoint ptOrigin
= m_combo
->ClientToScreen(wxPoint(0,0));
503 #if 0 //def __WXUNIVERSAL__
504 // account for the fact that (0, 0) is not the top left corner of the
505 // window: there is also the border
506 wxRect rectBorders
= m_combo
->GetRenderer()->
507 GetBorderDimensions(m_combo
->GetBorder());
508 ptOrigin
.x
-= rectBorders
.x
;
509 ptOrigin
.y
-= rectBorders
.y
;
510 #endif // __WXUNIVERSAL__
512 // position below or above the combobox: the width is 0 to put it exactly
513 // below us, not to the left or to the right
514 Position(ptOrigin
, wxSize(0, m_combo
->GetSize().y
));
517 void wxPopupComboWindow::OnDismiss()
519 m_combo
->OnPopupDismiss(true);
522 void wxPopupComboWindow::OnKeyDown(wxKeyEvent
& event
)
524 m_combo
->ProcessWindowEvent(event
);
527 #endif // wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
529 // ----------------------------------------------------------------------------
530 // wxPopupWindowHandler
531 // ----------------------------------------------------------------------------
533 void wxPopupWindowHandler::OnLeftDown(wxMouseEvent
& event
)
535 // let the window have it first (we're the first event handler in the chain
536 // of handlers for this window)
537 if ( m_popup
->ProcessLeftDown(event
) )
542 wxPoint pos
= event
.GetPosition();
544 // in non-Univ ports the system manages scrollbars for us
545 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
546 // scrollbar on which the click occurred
547 wxWindow
*sbar
= NULL
;
548 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
550 wxWindow
*win
= (wxWindow
*)event
.GetEventObject();
552 switch ( win
->HitTest(pos
.x
, pos
.y
) )
554 case wxHT_WINDOW_OUTSIDE
:
556 // do the coords translation now as after DismissAndNotify()
557 // m_popup may be destroyed
558 wxMouseEvent
event2(event
);
560 m_popup
->ClientToScreen(&event2
.m_x
, &event2
.m_y
);
562 // clicking outside a popup dismisses it
563 m_popup
->DismissAndNotify();
565 // dismissing a tooltip shouldn't waste a click, i.e. you
566 // should be able to dismiss it and press the button with the
567 // same click, so repost this event to the window beneath us
568 wxWindow
*winUnder
= wxFindWindowAtPoint(event2
.GetPosition());
571 // translate the event coords to the ones of the window
572 // which is going to get the event
573 winUnder
->ScreenToClient(&event2
.m_x
, &event2
.m_y
);
575 event2
.SetEventObject(winUnder
);
576 wxPostEvent(winUnder
->GetEventHandler(), event2
);
581 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
582 case wxHT_WINDOW_HORZ_SCROLLBAR
:
583 sbar
= win
->GetScrollbar(wxHORIZONTAL
);
586 case wxHT_WINDOW_VERT_SCROLLBAR
:
587 sbar
= win
->GetScrollbar(wxVERTICAL
);
589 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
592 // forgot to update the switch after adding a new hit test code?
593 wxFAIL_MSG( wxT("unexpected HitTest() return value") );
596 case wxHT_WINDOW_CORNER
:
597 // don't actually know if this one is good for anything, but let it
600 case wxHT_WINDOW_INSIDE
:
601 // let the normal processing take place
606 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
609 // translate the event coordinates to the scrollbar ones
610 pos
= sbar
->ScreenToClient(win
->ClientToScreen(pos
));
612 // and give the event to it
613 wxMouseEvent event2
= event
;
617 (void)sbar
->GetEventHandler()->ProcessEvent(event2
);
619 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
623 wxPopupWindowHandler::OnCaptureLost(wxMouseCaptureLostEvent
& WXUNUSED(event
))
625 m_popup
->DismissAndNotify();
627 // There is no need to skip the event here, normally we've already dealt
628 // with the focus loss.
631 // ----------------------------------------------------------------------------
632 // wxPopupFocusHandler
633 // ----------------------------------------------------------------------------
635 void wxPopupFocusHandler::OnKillFocus(wxFocusEvent
& event
)
637 // when we lose focus we always disappear - unless it goes to the popup (in
638 // which case we don't really lose it)
639 wxWindow
*win
= event
.GetWindow();
642 if ( win
== m_popup
)
644 win
= win
->GetParent();
647 m_popup
->DismissAndNotify();
650 void wxPopupFocusHandler::OnChar(wxKeyEvent
& event
)
652 // we can be associated with the popup itself in which case we should avoid
653 // infinite recursion
655 wxRecursionGuard
guard(s_inside
);
656 if ( guard
.IsInside() )
662 // let the window have it first, it might process the keys
663 if ( !m_popup
->GetEventHandler()->ProcessEvent(event
) )
665 // by default, dismiss the popup
666 m_popup
->DismissAndNotify();
670 #endif // wxUSE_POPUPWIN