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