Fixes for wxPopupTransientWindow for wxMSW. Includes Patch #1181190,
[wxWidgets.git] / src / common / popupcmn.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: 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 // License: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
21 #pragma implementation "popupwinbase.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #if wxUSE_POPUPWIN
32
33 #include "wx/popupwin.h"
34
35 #ifndef WX_PRECOMP
36 #include "wx/combobox.h" // wxComboControl
37 #include "wx/app.h" // wxPostEvent
38 #include "wx/log.h"
39 #include "wx/app.h"
40 #endif //WX_PRECOMP
41
42 #ifdef __WXUNIVERSAL__
43 #include "wx/univ/renderer.h"
44 #endif // __WXUNIVERSAL__
45
46 #ifdef __WXGTK__
47 #include <gtk/gtk.h>
48 #endif
49 #ifdef __WXX11__
50 #include "wx/x11/private.h"
51 #endif
52
53 IMPLEMENT_DYNAMIC_CLASS(wxPopupWindow, wxWindow)
54 IMPLEMENT_DYNAMIC_CLASS(wxPopupTransientWindow, wxPopupWindow)
55
56 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
57 IMPLEMENT_DYNAMIC_CLASS(wxPopupComboWindow, wxPopupTransientWindow)
58 #endif
59
60 // ----------------------------------------------------------------------------
61 // private classes
62 // ----------------------------------------------------------------------------
63
64 // event handlers which we use to intercept events which cause the popup to
65 // disappear
66 class wxPopupWindowHandler : public wxEvtHandler
67 {
68 public:
69 wxPopupWindowHandler(wxPopupTransientWindow *popup) : m_popup(popup) {}
70 ~wxPopupWindowHandler();
71
72 protected:
73 // event handlers
74 void OnLeftDown(wxMouseEvent& event);
75
76 private:
77 wxPopupTransientWindow *m_popup;
78
79 DECLARE_EVENT_TABLE()
80 DECLARE_NO_COPY_CLASS(wxPopupWindowHandler)
81 };
82
83 class wxPopupFocusHandler : public wxEvtHandler
84 {
85 public:
86 wxPopupFocusHandler(wxPopupTransientWindow *popup) : m_popup(popup) {}
87 ~wxPopupFocusHandler();
88
89 protected:
90 void OnKillFocus(wxFocusEvent& event);
91 void OnKeyDown(wxKeyEvent& event);
92
93 private:
94 wxPopupTransientWindow *m_popup;
95
96 DECLARE_EVENT_TABLE()
97 DECLARE_NO_COPY_CLASS(wxPopupFocusHandler)
98 };
99
100 // ----------------------------------------------------------------------------
101 // event tables
102 // ----------------------------------------------------------------------------
103
104 BEGIN_EVENT_TABLE(wxPopupWindowHandler, wxEvtHandler)
105 EVT_LEFT_DOWN(wxPopupWindowHandler::OnLeftDown)
106 END_EVENT_TABLE()
107
108 BEGIN_EVENT_TABLE(wxPopupFocusHandler, wxEvtHandler)
109 EVT_KILL_FOCUS(wxPopupFocusHandler::OnKillFocus)
110 EVT_KEY_DOWN(wxPopupFocusHandler::OnKeyDown)
111 END_EVENT_TABLE()
112
113 // ============================================================================
114 // implementation
115 // ============================================================================
116
117 // ----------------------------------------------------------------------------
118 // wxPopupWindowBase
119 // ----------------------------------------------------------------------------
120
121 wxPopupWindowBase::~wxPopupWindowBase()
122 {
123 // this destructor is required for Darwin
124 }
125
126 bool wxPopupWindowBase::Create(wxWindow* WXUNUSED(parent), int WXUNUSED(flags))
127 {
128 return true;
129 }
130
131 void wxPopupWindowBase::Position(const wxPoint& ptOrigin,
132 const wxSize& size)
133 {
134 wxSize sizeScreen = wxGetDisplaySize(),
135 sizeSelf = GetSize();
136
137 // is there enough space to put the popup below the window (where we put it
138 // by default)?
139 wxCoord y = ptOrigin.y + size.y;
140 if ( y + sizeSelf.y > sizeScreen.y )
141 {
142 // check if there is enough space above
143 if ( ptOrigin.y > sizeSelf.y )
144 {
145 // do position the control above the window
146 y -= size.y + sizeSelf.y;
147 }
148 //else: not enough space below nor above, leave below
149 }
150
151 // now check left/right too
152 wxCoord x = ptOrigin.x + size.x;
153 if ( x + sizeSelf.x > sizeScreen.x )
154 {
155 // check if there is enough space to the left
156 if ( ptOrigin.x > sizeSelf.x )
157 {
158 // do position the control to the left
159 x -= size.x + sizeSelf.x;
160 }
161 //else: not enough space there neither, leave in default position
162 }
163
164 Move(x, y, wxSIZE_NO_ADJUSTMENTS);
165 }
166
167 // ----------------------------------------------------------------------------
168 // wxPopupTransientWindow
169 // ----------------------------------------------------------------------------
170
171 BEGIN_EVENT_TABLE(wxPopupTransientWindow, wxPopupWindow)
172 #ifdef __WXMSW__
173 EVT_ENTER_WINDOW(wxPopupTransientWindow::OnEnter)
174 EVT_LEAVE_WINDOW(wxPopupTransientWindow::OnLeave)
175 EVT_LEFT_DOWN(wxPopupTransientWindow::OnLeftDown)
176 #endif
177 END_EVENT_TABLE()
178
179 void wxPopupTransientWindow::Init()
180 {
181 m_child =
182 m_focus = (wxWindow *)NULL;
183
184 m_handlerFocus = NULL;
185 m_handlerPopup = NULL;
186 }
187
188 wxPopupTransientWindow::wxPopupTransientWindow(wxWindow *parent, int style)
189 {
190 Init();
191
192 (void)Create(parent, style);
193 }
194
195 wxPopupTransientWindow::~wxPopupTransientWindow()
196 {
197 PopHandlers();
198 }
199
200 void wxPopupTransientWindow::PopHandlers()
201 {
202 if ( m_child )
203 {
204 if ( m_handlerPopup && !m_child->RemoveEventHandler(m_handlerPopup) )
205 {
206 // something is very wrong and someone else probably deleted our
207 // handler - so don't risk deleting it second time
208 m_handlerPopup = NULL;
209 }
210
211 m_child = NULL;
212 }
213
214 #ifdef __WXMSW__
215 if ( HasCapture() )
216 {
217 ReleaseCapture();
218 }
219 if ( m_focus )
220 {
221 if ( m_handlerFocus && !m_focus->RemoveEventHandler(m_handlerFocus) )
222 {
223 // see above
224 m_handlerFocus = NULL;
225 }
226 }
227 #else
228 if ( m_handlerFocus && !RemoveEventHandler(m_handlerFocus) )
229 {
230 // see above
231 m_handlerFocus = NULL;
232 }
233 #endif
234
235 // delete the handlers, they'll be created as necessary in Popup()
236 delete m_handlerPopup;
237 m_handlerPopup = NULL;
238 delete m_handlerFocus;
239 m_handlerFocus = NULL;
240
241 m_focus = NULL;
242 }
243
244 void wxPopupTransientWindow::Popup(wxWindow *winFocus)
245 {
246 const wxWindowList& children = GetChildren();
247 if ( children.GetCount() )
248 {
249 m_child = children.GetFirst()->GetData();
250 }
251 else
252 {
253 m_child = this;
254 }
255
256 Show();
257
258 // There is is a problem if these are still valid
259 wxASSERT_MSG(!m_handlerPopup, wxT("Popup handler not deleted"));
260 wxASSERT_MSG(!m_handlerFocus, wxT("Focus handler not deleted"));
261
262 delete m_handlerPopup;
263 m_handlerPopup = new wxPopupWindowHandler(this);
264
265 m_child->PushEventHandler(m_handlerPopup);
266
267 m_focus = winFocus ? winFocus : this;
268 m_focus->SetFocus();
269
270 #ifdef __WXMSW__
271 // MSW doesn't allow to set focus to the popup window, but we need to
272 // subclass the window which has the focus, and not winFocus passed in or
273 // otherwise everything else breaks down
274 m_focus = FindFocus();
275 if ( m_focus )
276 {
277 delete m_handlerFocus;
278 m_handlerFocus = new wxPopupFocusHandler(this);
279
280 m_focus->PushEventHandler(m_handlerFocus);
281 }
282 #else
283 // GTK+ catches the activate events from the popup
284 // window, not the focus events from the child window
285 delete m_handlerFocus;
286 m_handlerFocus = new wxPopupFocusHandler(this);
287 PushEventHandler(m_handlerFocus);
288 #endif // __WXMSW__
289
290 // catch destroy events, if you close a program with a popup shown in MSW
291 // you get a segfault if m_child or m_focus are deleted before this is
292 m_child->Connect(wxEVT_DESTROY,
293 wxWindowDestroyEventHandler(wxPopupTransientWindow::OnDestroy),
294 NULL, this);
295 m_focus->Connect(wxEVT_DESTROY,
296 wxWindowDestroyEventHandler(wxPopupTransientWindow::OnDestroy),
297 NULL, this);
298 #ifdef __WXMSW__
299 // Assume that the mouse is currently outside of the popup window
300 CaptureMouse();
301
302 // Connect the child Enter/Leave events too, incase the child completly
303 // covers the popup (because then the popup's enter/leave events won't be
304 // sent.
305 if (m_child != this)
306 {
307 m_child->Connect(wxEVT_ENTER_WINDOW,
308 wxMouseEventHandler(wxPopupTransientWindow::OnChildEnter),
309 NULL, this);
310 m_child->Connect(wxEVT_LEAVE_WINDOW,
311 wxMouseEventHandler(wxPopupTransientWindow::OnChildLeave),
312 NULL, this);
313 }
314 #endif
315 }
316
317 bool wxPopupTransientWindow::Show( bool show )
318 {
319 #ifdef __WXGTK__
320 if (!show)
321 {
322 gdk_pointer_ungrab( (guint32)GDK_CURRENT_TIME );
323
324 gtk_grab_remove( m_widget );
325 }
326 #endif
327
328 #ifdef __WXX11__
329 if (!show)
330 {
331 XUngrabPointer( wxGlobalDisplay(), CurrentTime );
332 }
333 #endif
334
335 bool ret = wxPopupWindow::Show( show );
336
337 #ifdef __WXGTK__
338 if (show)
339 {
340 gtk_grab_add( m_widget );
341
342 gdk_pointer_grab( m_widget->window, TRUE,
343 (GdkEventMask)
344 (GDK_BUTTON_PRESS_MASK |
345 GDK_BUTTON_RELEASE_MASK |
346 GDK_POINTER_MOTION_HINT_MASK |
347 GDK_POINTER_MOTION_MASK),
348 (GdkWindow *) NULL,
349 (GdkCursor *) NULL,
350 (guint32)GDK_CURRENT_TIME );
351 }
352 #endif
353
354 #ifdef __WXX11__
355 if (show)
356 {
357 Window xwindow = (Window) m_clientWindow;
358
359 /* int res =*/ XGrabPointer(wxGlobalDisplay(), xwindow,
360 True,
361 ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask,
362 GrabModeAsync,
363 GrabModeAsync,
364 None,
365 None,
366 CurrentTime );
367 }
368 #endif
369 return ret;
370 }
371
372 void wxPopupTransientWindow::Dismiss()
373 {
374 PopHandlers();
375
376 Hide();
377 }
378
379 void wxPopupTransientWindow::DismissAndNotify()
380 {
381 Dismiss();
382
383 OnDismiss();
384 }
385
386 void wxPopupTransientWindow::OnDismiss()
387 {
388 // nothing to do here - but it may be interesting for derived class
389 }
390
391 bool wxPopupTransientWindow::ProcessLeftDown(wxMouseEvent& WXUNUSED(event))
392 {
393 // no special processing here
394 return false;
395 }
396
397 void wxPopupTransientWindow::OnDestroy(wxWindowDestroyEvent& event)
398 {
399 if (event.GetEventObject() == m_child)
400 m_child = NULL;
401 if (event.GetEventObject() == m_focus)
402 m_focus = NULL;
403 }
404
405 void wxPopupTransientWindow::OnEnter(wxMouseEvent& /*event*/)
406 {
407 if ( HasCapture() )
408 {
409 ReleaseCapture();
410 }
411 }
412
413 void wxPopupTransientWindow::OnLeave(wxMouseEvent& /*event*/)
414 {
415 CaptureMouse();
416 }
417
418 void wxPopupTransientWindow::OnLeftDown(wxMouseEvent& event)
419 {
420 if (m_handlerPopup && m_child && m_child != this)
421 {
422 m_child->GetEventHandler()->ProcessEvent(event);
423 }
424 }
425
426
427
428 // If the child is the same size as the popup window then handle the event,
429 // otherwise assume that there is enough of the popup showing that it will get
430 // it's own Enter/Leave events. A more reliable way to detect this situation
431 // would be appreciated...
432
433 void wxPopupTransientWindow::OnChildEnter(wxMouseEvent& event)
434 {
435 if (m_child)
436 {
437 wxSize cs = m_child->GetSize();
438 wxSize ps = GetClientSize();
439
440 if ((cs.x * cs.y) >= (ps.x * ps.y))
441 OnEnter(event);
442 }
443 }
444
445 void wxPopupTransientWindow::OnChildLeave(wxMouseEvent& event)
446 {
447 if (m_child)
448 {
449 wxSize cs = m_child->GetSize();
450 wxSize ps = GetClientSize();
451
452 if ((cs.x * cs.y) >= (ps.x * ps.y))
453 OnLeave(event);
454 }
455 }
456
457 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
458
459 // ----------------------------------------------------------------------------
460 // wxPopupComboWindow
461 // ----------------------------------------------------------------------------
462
463 BEGIN_EVENT_TABLE(wxPopupComboWindow, wxPopupTransientWindow)
464 EVT_KEY_DOWN(wxPopupComboWindow::OnKeyDown)
465 END_EVENT_TABLE()
466
467 wxPopupComboWindow::wxPopupComboWindow(wxComboControl *parent)
468 : wxPopupTransientWindow(parent)
469 {
470 m_combo = parent;
471 }
472
473 bool wxPopupComboWindow::Create(wxComboControl *parent)
474 {
475 m_combo = parent;
476
477 return wxPopupWindow::Create(parent);
478 }
479
480 void wxPopupComboWindow::PositionNearCombo()
481 {
482 // the origin point must be in screen coords
483 wxPoint ptOrigin = m_combo->ClientToScreen(wxPoint(0,0));
484
485 #if 0 //def __WXUNIVERSAL__
486 // account for the fact that (0, 0) is not the top left corner of the
487 // window: there is also the border
488 wxRect rectBorders = m_combo->GetRenderer()->
489 GetBorderDimensions(m_combo->GetBorder());
490 ptOrigin.x -= rectBorders.x;
491 ptOrigin.y -= rectBorders.y;
492 #endif // __WXUNIVERSAL__
493
494 // position below or above the combobox: the width is 0 to put it exactly
495 // below us, not to the left or to the right
496 Position(ptOrigin, wxSize(0, m_combo->GetSize().y));
497 }
498
499 void wxPopupComboWindow::OnDismiss()
500 {
501 m_combo->OnDismiss();
502 }
503
504 void wxPopupComboWindow::OnKeyDown(wxKeyEvent& event)
505 {
506 m_combo->ProcessEvent(event);
507 }
508
509 #endif // wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
510
511 // ----------------------------------------------------------------------------
512 // wxPopupWindowHandler
513 // ----------------------------------------------------------------------------
514 wxPopupWindowHandler::~wxPopupWindowHandler()
515 {
516 if (m_popup && (m_popup->m_handlerPopup == this))
517 m_popup->m_handlerPopup = NULL;
518 }
519
520 void wxPopupWindowHandler::OnLeftDown(wxMouseEvent& event)
521 {
522 // let the window have it first (we're the first event handler in the chain
523 // of handlers for this window)
524 if ( m_popup->ProcessLeftDown(event) )
525 {
526 return;
527 }
528
529 wxPoint pos = event.GetPosition();
530
531 // scrollbar on which the click occured
532 wxWindow *sbar = NULL;
533
534 wxWindow *win = (wxWindow *)event.GetEventObject();
535
536 switch ( win->HitTest(pos.x, pos.y) )
537 {
538 case wxHT_WINDOW_OUTSIDE:
539 {
540 // do the coords translation now as after DismissAndNotify()
541 // m_popup may be destroyed
542 wxMouseEvent event2(event);
543
544 m_popup->ClientToScreen(&event2.m_x, &event2.m_y);
545
546 // clicking outside a popup dismisses it
547 m_popup->DismissAndNotify();
548
549 // dismissing a tooltip shouldn't waste a click, i.e. you
550 // should be able to dismiss it and press the button with the
551 // same click, so repost this event to the window beneath us
552 wxWindow *win = wxFindWindowAtPoint(event2.GetPosition());
553 if ( win )
554 {
555 // translate the event coords to the ones of the window
556 // which is going to get the event
557 win->ScreenToClient(&event2.m_x, &event2.m_y);
558
559 event2.SetEventObject(win);
560 wxPostEvent(win, event2);
561 }
562 }
563 break;
564
565 #ifdef __WXUNIVERSAL__
566 case wxHT_WINDOW_HORZ_SCROLLBAR:
567 sbar = win->GetScrollbar(wxHORIZONTAL);
568 break;
569
570 case wxHT_WINDOW_VERT_SCROLLBAR:
571 sbar = win->GetScrollbar(wxVERTICAL);
572 break;
573 #endif
574
575 default:
576 // forgot to update the switch after adding a new hit test code?
577 wxFAIL_MSG( _T("unexpected HitTest() return value") );
578 // fall through
579
580 case wxHT_WINDOW_CORNER:
581 // don't actually know if this one is good for anything, but let it
582 // pass just in case
583
584 case wxHT_WINDOW_INSIDE:
585 // let the normal processing take place
586 event.Skip();
587 break;
588 }
589
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 }
603
604 // ----------------------------------------------------------------------------
605 // wxPopupFocusHandler
606 // ----------------------------------------------------------------------------
607 wxPopupFocusHandler::~wxPopupFocusHandler()
608 {
609 if (m_popup && (m_popup->m_handlerFocus == this))
610 m_popup->m_handlerFocus = NULL;
611 }
612
613 void wxPopupFocusHandler::OnKillFocus(wxFocusEvent& event)
614 {
615 // when we lose focus we always disappear - unless it goes to the popup (in
616 // which case we don't really lose it)
617 wxWindow *win = event.GetWindow();
618 while ( win )
619 {
620 if ( win == m_popup )
621 return;
622 win = win->GetParent();
623 }
624
625 m_popup->DismissAndNotify();
626 }
627
628 void wxPopupFocusHandler::OnKeyDown(wxKeyEvent& event)
629 {
630 // let the window have it first, it might process the keys
631 if ( !m_popup->ProcessEvent(event) )
632 {
633 // by default, dismiss the popup
634 m_popup->DismissAndNotify();
635 }
636 }
637
638 #endif // wxUSE_POPUPWIN
639