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 #include "wx/gtk/private/gtk2-compat.h"
48 #elif defined(__WXMSW__)
49 #include "wx/msw/private.h"
50 #elif defined(__WXX11__)
51 #include "wx/x11/private.h"
54 IMPLEMENT_DYNAMIC_CLASS(wxPopupWindow
, wxWindow
)
55 IMPLEMENT_DYNAMIC_CLASS(wxPopupTransientWindow
, wxPopupWindow
)
57 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
58 IMPLEMENT_DYNAMIC_CLASS(wxPopupComboWindow
, wxPopupTransientWindow
)
61 // ----------------------------------------------------------------------------
63 // ----------------------------------------------------------------------------
65 // event handlers which we use to intercept events which cause the popup to
67 class wxPopupWindowHandler
: public wxEvtHandler
70 wxPopupWindowHandler(wxPopupTransientWindow
*popup
) : m_popup(popup
) {}
74 void OnLeftDown(wxMouseEvent
& event
);
77 wxPopupTransientWindow
*m_popup
;
80 wxDECLARE_NO_COPY_CLASS(wxPopupWindowHandler
);
83 class wxPopupFocusHandler
: public wxEvtHandler
86 wxPopupFocusHandler(wxPopupTransientWindow
*popup
) : m_popup(popup
) {}
89 void OnKillFocus(wxFocusEvent
& event
);
90 void OnChar(wxKeyEvent
& event
);
93 wxPopupTransientWindow
*m_popup
;
96 wxDECLARE_NO_COPY_CLASS(wxPopupFocusHandler
);
99 // ----------------------------------------------------------------------------
101 // ----------------------------------------------------------------------------
103 BEGIN_EVENT_TABLE(wxPopupWindowHandler
, wxEvtHandler
)
104 EVT_LEFT_DOWN(wxPopupWindowHandler::OnLeftDown
)
107 BEGIN_EVENT_TABLE(wxPopupFocusHandler
, wxEvtHandler
)
108 EVT_KILL_FOCUS(wxPopupFocusHandler::OnKillFocus
)
109 EVT_CHAR(wxPopupFocusHandler::OnChar
)
112 BEGIN_EVENT_TABLE(wxPopupTransientWindow
, wxPopupWindow
)
113 #if defined( __WXMSW__ ) || ( defined( __WXMAC__ ) && wxOSX_USE_CARBON )
114 EVT_IDLE(wxPopupTransientWindow::OnIdle
)
118 // ============================================================================
120 // ============================================================================
122 // ----------------------------------------------------------------------------
124 // ----------------------------------------------------------------------------
126 wxPopupWindowBase::~wxPopupWindowBase()
128 // this destructor is required for Darwin
131 bool wxPopupWindowBase::Create(wxWindow
* WXUNUSED(parent
), int WXUNUSED(flags
))
136 void wxPopupWindowBase::Position(const wxPoint
& ptOrigin
,
139 // determine the position and size of the screen we clamp the popup to
143 const int displayNum
= wxDisplay::GetFromPoint(ptOrigin
);
144 if ( displayNum
!= wxNOT_FOUND
)
146 const wxRect rectScreen
= wxDisplay(displayNum
).GetGeometry();
147 posScreen
= rectScreen
.GetPosition();
148 sizeScreen
= rectScreen
.GetSize();
150 else // outside of any display?
152 // just use the primary one then
153 posScreen
= wxPoint(0, 0);
154 sizeScreen
= wxGetDisplaySize();
158 const wxSize sizeSelf
= GetSize();
160 // is there enough space to put the popup below the window (where we put it
162 wxCoord y
= ptOrigin
.y
+ size
.y
;
163 if ( y
+ sizeSelf
.y
> posScreen
.y
+ sizeScreen
.y
)
165 // check if there is enough space above
166 if ( ptOrigin
.y
> sizeSelf
.y
)
168 // do position the control above the window
169 y
-= size
.y
+ sizeSelf
.y
;
171 //else: not enough space below nor above, leave below
174 // now check left/right too
175 wxCoord x
= ptOrigin
.x
;
177 if ( wxTheApp
->GetLayoutDirection() == wxLayout_RightToLeft
)
179 // shift the window to the left instead of the right.
181 x
-= sizeSelf
.x
; // also shift it by window width.
187 if ( x
+ sizeSelf
.x
> posScreen
.x
+ sizeScreen
.x
)
189 // check if there is enough space to the left
190 if ( ptOrigin
.x
> sizeSelf
.x
)
192 // do position the control to the left
193 x
-= size
.x
+ sizeSelf
.x
;
195 //else: not enough space there neither, leave in default position
198 Move(x
, y
, wxSIZE_NO_ADJUSTMENTS
);
201 // ----------------------------------------------------------------------------
202 // wxPopupTransientWindow
203 // ----------------------------------------------------------------------------
205 void wxPopupTransientWindow::Init()
210 m_handlerFocus
= NULL
;
211 m_handlerPopup
= NULL
;
214 wxPopupTransientWindow::wxPopupTransientWindow(wxWindow
*parent
, int style
)
218 (void)Create(parent
, style
);
221 wxPopupTransientWindow::~wxPopupTransientWindow()
223 if (m_handlerPopup
&& m_handlerPopup
->GetNextHandler())
226 wxASSERT(!m_handlerFocus
|| !m_handlerFocus
->GetNextHandler());
227 wxASSERT(!m_handlerPopup
|| !m_handlerPopup
->GetNextHandler());
229 delete m_handlerFocus
;
230 delete m_handlerPopup
;
233 void wxPopupTransientWindow::PopHandlers()
237 if ( !m_child
->RemoveEventHandler(m_handlerPopup
) )
239 // something is very wrong and someone else probably deleted our
240 // handler - so don't risk deleting it second time
241 m_handlerPopup
= NULL
;
243 if (m_child
->HasCapture())
245 m_child
->ReleaseMouse();
252 if ( !m_focus
->RemoveEventHandler(m_handlerFocus
) )
255 m_handlerFocus
= NULL
;
261 void wxPopupTransientWindow::Popup(wxWindow
*winFocus
)
263 const wxWindowList
& children
= GetChildren();
264 if ( children
.GetCount() )
266 m_child
= children
.GetFirst()->GetData();
275 // There is a problem if these are still in use
276 wxASSERT(!m_handlerFocus
|| !m_handlerFocus
->GetNextHandler());
277 wxASSERT(!m_handlerPopup
|| !m_handlerPopup
->GetNextHandler());
280 m_handlerPopup
= new wxPopupWindowHandler(this);
282 m_child
->PushEventHandler(m_handlerPopup
);
284 #if defined(__WXMSW__)
285 // Focusing on child of popup window does not work on MSW unless WS_POPUP
286 // style is set. We do not even want to try to set the focus, as it may
287 // provoke errors on some Windows versions (Vista and later).
288 if ( ::GetWindowLong(GetHwnd(), GWL_STYLE
) & WS_POPUP
)
291 m_focus
= winFocus
? winFocus
: this;
295 #if defined( __WXMSW__ ) || (defined( __WXMAC__) && wxOSX_USE_CARBON)
296 // MSW doesn't allow to set focus to the popup window, but we need to
297 // subclass the window which has the focus, and not winFocus passed in or
298 // otherwise everything else breaks down
299 m_focus
= FindFocus();
300 #elif defined(__WXGTK__)
301 // GTK+ catches the activate events from the popup
302 // window, not the focus events from the child window
309 m_handlerFocus
= new wxPopupFocusHandler(this);
311 m_focus
->PushEventHandler(m_handlerFocus
);
315 bool wxPopupTransientWindow::Show( bool show
)
320 gdk_pointer_ungrab( (guint32
)GDK_CURRENT_TIME
);
322 gtk_grab_remove( m_widget
);
329 XUngrabPointer( wxGlobalDisplay(), CurrentTime
);
333 #if defined( __WXMSW__ ) || defined( __WXMAC__)
334 if (!show
&& m_child
&& m_child
->HasCapture())
336 m_child
->ReleaseMouse();
340 bool ret
= wxPopupWindow::Show( show
);
345 gtk_grab_add( m_widget
);
347 gdk_pointer_grab( gtk_widget_get_window(m_widget
), true,
349 (GDK_BUTTON_PRESS_MASK
|
350 GDK_BUTTON_RELEASE_MASK
|
351 GDK_POINTER_MOTION_HINT_MASK
|
352 GDK_POINTER_MOTION_MASK
),
355 (guint32
)GDK_CURRENT_TIME
);
362 Window xwindow
= (Window
) m_clientWindow
;
364 /* int res =*/ XGrabPointer(wxGlobalDisplay(), xwindow
,
366 ButtonPressMask
| ButtonReleaseMask
| ButtonMotionMask
| EnterWindowMask
| LeaveWindowMask
| PointerMotionMask
,
375 #if defined( __WXMSW__ ) || defined( __WXMAC__)
378 // Assume that the mouse is outside the popup to begin with
379 m_child
->CaptureMouse();
386 void wxPopupTransientWindow::Dismiss()
392 void wxPopupTransientWindow::DismissAndNotify()
398 void wxPopupTransientWindow::OnDismiss()
400 // nothing to do here - but it may be interesting for derived class
403 bool wxPopupTransientWindow::ProcessLeftDown(wxMouseEvent
& WXUNUSED(event
))
405 // no special processing here
409 #if defined( __WXMSW__ ) || ( defined( __WXMAC__ ) && wxOSX_USE_CARBON )
410 void wxPopupTransientWindow::OnIdle(wxIdleEvent
& event
)
414 if (IsShown() && m_child
)
416 wxPoint pos
= ScreenToClient(wxGetMousePosition());
417 wxRect
rect(GetSize());
419 if ( rect
.Contains(pos
) )
421 if ( m_child
->HasCapture() )
423 m_child
->ReleaseMouse();
428 if ( !m_child
->HasCapture() )
430 m_child
->CaptureMouse();
438 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
440 // ----------------------------------------------------------------------------
441 // wxPopupComboWindow
442 // ----------------------------------------------------------------------------
444 BEGIN_EVENT_TABLE(wxPopupComboWindow
, wxPopupTransientWindow
)
445 EVT_KEY_DOWN(wxPopupComboWindow::OnKeyDown
)
448 wxPopupComboWindow::wxPopupComboWindow(wxComboCtrl
*parent
)
449 : wxPopupTransientWindow(parent
)
454 bool wxPopupComboWindow::Create(wxComboCtrl
*parent
)
458 return wxPopupWindow::Create(parent
);
461 void wxPopupComboWindow::PositionNearCombo()
463 // the origin point must be in screen coords
464 wxPoint ptOrigin
= m_combo
->ClientToScreen(wxPoint(0,0));
466 #if 0 //def __WXUNIVERSAL__
467 // account for the fact that (0, 0) is not the top left corner of the
468 // window: there is also the border
469 wxRect rectBorders
= m_combo
->GetRenderer()->
470 GetBorderDimensions(m_combo
->GetBorder());
471 ptOrigin
.x
-= rectBorders
.x
;
472 ptOrigin
.y
-= rectBorders
.y
;
473 #endif // __WXUNIVERSAL__
475 // position below or above the combobox: the width is 0 to put it exactly
476 // below us, not to the left or to the right
477 Position(ptOrigin
, wxSize(0, m_combo
->GetSize().y
));
480 void wxPopupComboWindow::OnDismiss()
482 m_combo
->OnPopupDismiss(true);
485 void wxPopupComboWindow::OnKeyDown(wxKeyEvent
& event
)
487 m_combo
->ProcessWindowEvent(event
);
490 #endif // wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
492 // ----------------------------------------------------------------------------
493 // wxPopupWindowHandler
494 // ----------------------------------------------------------------------------
496 void wxPopupWindowHandler::OnLeftDown(wxMouseEvent
& event
)
498 // let the window have it first (we're the first event handler in the chain
499 // of handlers for this window)
500 if ( m_popup
->ProcessLeftDown(event
) )
505 wxPoint pos
= event
.GetPosition();
507 // in non-Univ ports the system manages scrollbars for us
508 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
509 // scrollbar on which the click occurred
510 wxWindow
*sbar
= NULL
;
511 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
513 wxWindow
*win
= (wxWindow
*)event
.GetEventObject();
515 switch ( win
->HitTest(pos
.x
, pos
.y
) )
517 case wxHT_WINDOW_OUTSIDE
:
519 // do the coords translation now as after DismissAndNotify()
520 // m_popup may be destroyed
521 wxMouseEvent
event2(event
);
523 m_popup
->ClientToScreen(&event2
.m_x
, &event2
.m_y
);
525 // clicking outside a popup dismisses it
526 m_popup
->DismissAndNotify();
528 // dismissing a tooltip shouldn't waste a click, i.e. you
529 // should be able to dismiss it and press the button with the
530 // same click, so repost this event to the window beneath us
531 wxWindow
*winUnder
= wxFindWindowAtPoint(event2
.GetPosition());
534 // translate the event coords to the ones of the window
535 // which is going to get the event
536 winUnder
->ScreenToClient(&event2
.m_x
, &event2
.m_y
);
538 event2
.SetEventObject(winUnder
);
539 wxPostEvent(winUnder
->GetEventHandler(), event2
);
544 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
545 case wxHT_WINDOW_HORZ_SCROLLBAR
:
546 sbar
= win
->GetScrollbar(wxHORIZONTAL
);
549 case wxHT_WINDOW_VERT_SCROLLBAR
:
550 sbar
= win
->GetScrollbar(wxVERTICAL
);
552 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
555 // forgot to update the switch after adding a new hit test code?
556 wxFAIL_MSG( wxT("unexpected HitTest() return value") );
559 case wxHT_WINDOW_CORNER
:
560 // don't actually know if this one is good for anything, but let it
563 case wxHT_WINDOW_INSIDE
:
564 // let the normal processing take place
569 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
572 // translate the event coordinates to the scrollbar ones
573 pos
= sbar
->ScreenToClient(win
->ClientToScreen(pos
));
575 // and give the event to it
576 wxMouseEvent event2
= event
;
580 (void)sbar
->GetEventHandler()->ProcessEvent(event2
);
582 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
585 // ----------------------------------------------------------------------------
586 // wxPopupFocusHandler
587 // ----------------------------------------------------------------------------
589 void wxPopupFocusHandler::OnKillFocus(wxFocusEvent
& event
)
591 // when we lose focus we always disappear - unless it goes to the popup (in
592 // which case we don't really lose it)
593 wxWindow
*win
= event
.GetWindow();
596 if ( win
== m_popup
)
598 win
= win
->GetParent();
601 m_popup
->DismissAndNotify();
604 void wxPopupFocusHandler::OnChar(wxKeyEvent
& event
)
606 // we can be associated with the popup itself in which case we should avoid
607 // infinite recursion
609 wxRecursionGuard
guard(s_inside
);
610 if ( guard
.IsInside() )
616 // let the window have it first, it might process the keys
617 if ( !m_popup
->GetEventHandler()->ProcessEvent(event
) )
619 // by default, dismiss the popup
620 m_popup
->DismissAndNotify();
624 #endif // wxUSE_POPUPWIN