avoid deprecated functions and direct struct access
[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 #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"
52 #endif
53
54 IMPLEMENT_DYNAMIC_CLASS(wxPopupWindow, wxWindow)
55 IMPLEMENT_DYNAMIC_CLASS(wxPopupTransientWindow, wxPopupWindow)
56
57 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
58 IMPLEMENT_DYNAMIC_CLASS(wxPopupComboWindow, wxPopupTransientWindow)
59 #endif
60
61 // ----------------------------------------------------------------------------
62 // private classes
63 // ----------------------------------------------------------------------------
64
65 // event handlers which we use to intercept events which cause the popup to
66 // disappear
67 class wxPopupWindowHandler : public wxEvtHandler
68 {
69 public:
70 wxPopupWindowHandler(wxPopupTransientWindow *popup) : m_popup(popup) {}
71
72 protected:
73 // event handlers
74 void OnLeftDown(wxMouseEvent& event);
75
76 private:
77 wxPopupTransientWindow *m_popup;
78
79 DECLARE_EVENT_TABLE()
80 wxDECLARE_NO_COPY_CLASS(wxPopupWindowHandler);
81 };
82
83 class wxPopupFocusHandler : public wxEvtHandler
84 {
85 public:
86 wxPopupFocusHandler(wxPopupTransientWindow *popup) : m_popup(popup) {}
87
88 protected:
89 void OnKillFocus(wxFocusEvent& event);
90 void OnChar(wxKeyEvent& event);
91
92 private:
93 wxPopupTransientWindow *m_popup;
94
95 DECLARE_EVENT_TABLE()
96 wxDECLARE_NO_COPY_CLASS(wxPopupFocusHandler);
97 };
98
99 // ----------------------------------------------------------------------------
100 // event tables
101 // ----------------------------------------------------------------------------
102
103 BEGIN_EVENT_TABLE(wxPopupWindowHandler, wxEvtHandler)
104 EVT_LEFT_DOWN(wxPopupWindowHandler::OnLeftDown)
105 END_EVENT_TABLE()
106
107 BEGIN_EVENT_TABLE(wxPopupFocusHandler, wxEvtHandler)
108 EVT_KILL_FOCUS(wxPopupFocusHandler::OnKillFocus)
109 EVT_CHAR(wxPopupFocusHandler::OnChar)
110 END_EVENT_TABLE()
111
112 BEGIN_EVENT_TABLE(wxPopupTransientWindow, wxPopupWindow)
113 #if defined( __WXMSW__ ) || ( defined( __WXMAC__ ) && wxOSX_USE_CARBON )
114 EVT_IDLE(wxPopupTransientWindow::OnIdle)
115 #endif
116 END_EVENT_TABLE()
117
118 // ============================================================================
119 // implementation
120 // ============================================================================
121
122 // ----------------------------------------------------------------------------
123 // wxPopupWindowBase
124 // ----------------------------------------------------------------------------
125
126 wxPopupWindowBase::~wxPopupWindowBase()
127 {
128 // this destructor is required for Darwin
129 }
130
131 bool wxPopupWindowBase::Create(wxWindow* WXUNUSED(parent), int WXUNUSED(flags))
132 {
133 return true;
134 }
135
136 void wxPopupWindowBase::Position(const wxPoint& ptOrigin,
137 const wxSize& size)
138 {
139 // determine the position and size of the screen we clamp the popup to
140 wxPoint posScreen;
141 wxSize sizeScreen;
142
143 const int displayNum = wxDisplay::GetFromPoint(ptOrigin);
144 if ( displayNum != wxNOT_FOUND )
145 {
146 const wxRect rectScreen = wxDisplay(displayNum).GetGeometry();
147 posScreen = rectScreen.GetPosition();
148 sizeScreen = rectScreen.GetSize();
149 }
150 else // outside of any display?
151 {
152 // just use the primary one then
153 posScreen = wxPoint(0, 0);
154 sizeScreen = wxGetDisplaySize();
155 }
156
157
158 const wxSize sizeSelf = GetSize();
159
160 // is there enough space to put the popup below the window (where we put it
161 // by default)?
162 wxCoord y = ptOrigin.y + size.y;
163 if ( y + sizeSelf.y > posScreen.y + sizeScreen.y )
164 {
165 // check if there is enough space above
166 if ( ptOrigin.y > sizeSelf.y )
167 {
168 // do position the control above the window
169 y -= size.y + sizeSelf.y;
170 }
171 //else: not enough space below nor above, leave below
172 }
173
174 // now check left/right too
175 wxCoord x = ptOrigin.x;
176
177 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
178 {
179 // shift the window to the left instead of the right.
180 x -= size.x;
181 x -= sizeSelf.x; // also shift it by window width.
182 }
183 else
184 x += size.x;
185
186
187 if ( x + sizeSelf.x > posScreen.x + sizeScreen.x )
188 {
189 // check if there is enough space to the left
190 if ( ptOrigin.x > sizeSelf.x )
191 {
192 // do position the control to the left
193 x -= size.x + sizeSelf.x;
194 }
195 //else: not enough space there neither, leave in default position
196 }
197
198 Move(x, y, wxSIZE_NO_ADJUSTMENTS);
199 }
200
201 // ----------------------------------------------------------------------------
202 // wxPopupTransientWindow
203 // ----------------------------------------------------------------------------
204
205 void wxPopupTransientWindow::Init()
206 {
207 m_child =
208 m_focus = NULL;
209
210 m_handlerFocus = NULL;
211 m_handlerPopup = NULL;
212 }
213
214 wxPopupTransientWindow::wxPopupTransientWindow(wxWindow *parent, int style)
215 {
216 Init();
217
218 (void)Create(parent, style);
219 }
220
221 wxPopupTransientWindow::~wxPopupTransientWindow()
222 {
223 if (m_handlerPopup && m_handlerPopup->GetNextHandler())
224 PopHandlers();
225
226 wxASSERT(!m_handlerFocus || !m_handlerFocus->GetNextHandler());
227 wxASSERT(!m_handlerPopup || !m_handlerPopup->GetNextHandler());
228
229 delete m_handlerFocus;
230 delete m_handlerPopup;
231 }
232
233 void wxPopupTransientWindow::PopHandlers()
234 {
235 if ( m_child )
236 {
237 if ( !m_child->RemoveEventHandler(m_handlerPopup) )
238 {
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;
242 }
243 if (m_child->HasCapture())
244 {
245 m_child->ReleaseMouse();
246 }
247 m_child = NULL;
248 }
249
250 if ( m_focus )
251 {
252 if ( !m_focus->RemoveEventHandler(m_handlerFocus) )
253 {
254 // see above
255 m_handlerFocus = NULL;
256 }
257 }
258 m_focus = NULL;
259 }
260
261 void wxPopupTransientWindow::Popup(wxWindow *winFocus)
262 {
263 const wxWindowList& children = GetChildren();
264 if ( children.GetCount() )
265 {
266 m_child = children.GetFirst()->GetData();
267 }
268 else
269 {
270 m_child = this;
271 }
272
273 Show();
274
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());
278
279 if (!m_handlerPopup)
280 m_handlerPopup = new wxPopupWindowHandler(this);
281
282 m_child->PushEventHandler(m_handlerPopup);
283
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 )
289 #endif
290 {
291 m_focus = winFocus ? winFocus : this;
292 m_focus->SetFocus();
293 }
294
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
303 m_focus = this;
304 #endif
305
306 if ( m_focus )
307 {
308 if (!m_handlerFocus)
309 m_handlerFocus = new wxPopupFocusHandler(this);
310
311 m_focus->PushEventHandler(m_handlerFocus);
312 }
313 }
314
315 bool wxPopupTransientWindow::Show( bool show )
316 {
317 #ifdef __WXGTK__
318 if (!show)
319 {
320 gdk_pointer_ungrab( (guint32)GDK_CURRENT_TIME );
321
322 gtk_grab_remove( m_widget );
323 }
324 #endif
325
326 #ifdef __WXX11__
327 if (!show)
328 {
329 XUngrabPointer( wxGlobalDisplay(), CurrentTime );
330 }
331 #endif
332
333 #if defined( __WXMSW__ ) || defined( __WXMAC__)
334 if (!show && m_child && m_child->HasCapture())
335 {
336 m_child->ReleaseMouse();
337 }
338 #endif
339
340 bool ret = wxPopupWindow::Show( show );
341
342 #ifdef __WXGTK__
343 if (show)
344 {
345 gtk_grab_add( m_widget );
346
347 gdk_pointer_grab( gtk_widget_get_window(m_widget), true,
348 (GdkEventMask)
349 (GDK_BUTTON_PRESS_MASK |
350 GDK_BUTTON_RELEASE_MASK |
351 GDK_POINTER_MOTION_HINT_MASK |
352 GDK_POINTER_MOTION_MASK),
353 NULL,
354 NULL,
355 (guint32)GDK_CURRENT_TIME );
356 }
357 #endif
358
359 #ifdef __WXX11__
360 if (show)
361 {
362 Window xwindow = (Window) m_clientWindow;
363
364 /* int res =*/ XGrabPointer(wxGlobalDisplay(), xwindow,
365 True,
366 ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask,
367 GrabModeAsync,
368 GrabModeAsync,
369 None,
370 None,
371 CurrentTime );
372 }
373 #endif
374
375 #if defined( __WXMSW__ ) || defined( __WXMAC__)
376 if (show && m_child)
377 {
378 // Assume that the mouse is outside the popup to begin with
379 m_child->CaptureMouse();
380 }
381 #endif
382
383 return ret;
384 }
385
386 void wxPopupTransientWindow::Dismiss()
387 {
388 Hide();
389 PopHandlers();
390 }
391
392 void wxPopupTransientWindow::DismissAndNotify()
393 {
394 Dismiss();
395 OnDismiss();
396 }
397
398 void wxPopupTransientWindow::OnDismiss()
399 {
400 // nothing to do here - but it may be interesting for derived class
401 }
402
403 bool wxPopupTransientWindow::ProcessLeftDown(wxMouseEvent& WXUNUSED(event))
404 {
405 // no special processing here
406 return false;
407 }
408
409 #if defined( __WXMSW__ ) || ( defined( __WXMAC__ ) && wxOSX_USE_CARBON )
410 void wxPopupTransientWindow::OnIdle(wxIdleEvent& event)
411 {
412 event.Skip();
413
414 if (IsShown() && m_child)
415 {
416 wxPoint pos = ScreenToClient(wxGetMousePosition());
417 wxRect rect(GetSize());
418
419 if ( rect.Contains(pos) )
420 {
421 if ( m_child->HasCapture() )
422 {
423 m_child->ReleaseMouse();
424 }
425 }
426 else
427 {
428 if ( !m_child->HasCapture() )
429 {
430 m_child->CaptureMouse();
431 }
432 }
433 }
434 }
435 #endif // __WXMSW__
436
437
438 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
439
440 // ----------------------------------------------------------------------------
441 // wxPopupComboWindow
442 // ----------------------------------------------------------------------------
443
444 BEGIN_EVENT_TABLE(wxPopupComboWindow, wxPopupTransientWindow)
445 EVT_KEY_DOWN(wxPopupComboWindow::OnKeyDown)
446 END_EVENT_TABLE()
447
448 wxPopupComboWindow::wxPopupComboWindow(wxComboCtrl *parent)
449 : wxPopupTransientWindow(parent)
450 {
451 m_combo = parent;
452 }
453
454 bool wxPopupComboWindow::Create(wxComboCtrl *parent)
455 {
456 m_combo = parent;
457
458 return wxPopupWindow::Create(parent);
459 }
460
461 void wxPopupComboWindow::PositionNearCombo()
462 {
463 // the origin point must be in screen coords
464 wxPoint ptOrigin = m_combo->ClientToScreen(wxPoint(0,0));
465
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__
474
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));
478 }
479
480 void wxPopupComboWindow::OnDismiss()
481 {
482 m_combo->OnPopupDismiss(true);
483 }
484
485 void wxPopupComboWindow::OnKeyDown(wxKeyEvent& event)
486 {
487 m_combo->ProcessWindowEvent(event);
488 }
489
490 #endif // wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
491
492 // ----------------------------------------------------------------------------
493 // wxPopupWindowHandler
494 // ----------------------------------------------------------------------------
495
496 void wxPopupWindowHandler::OnLeftDown(wxMouseEvent& event)
497 {
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) )
501 {
502 return;
503 }
504
505 wxPoint pos = event.GetPosition();
506
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
512
513 wxWindow *win = (wxWindow *)event.GetEventObject();
514
515 switch ( win->HitTest(pos.x, pos.y) )
516 {
517 case wxHT_WINDOW_OUTSIDE:
518 {
519 // do the coords translation now as after DismissAndNotify()
520 // m_popup may be destroyed
521 wxMouseEvent event2(event);
522
523 m_popup->ClientToScreen(&event2.m_x, &event2.m_y);
524
525 // clicking outside a popup dismisses it
526 m_popup->DismissAndNotify();
527
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());
532 if ( winUnder )
533 {
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);
537
538 event2.SetEventObject(winUnder);
539 wxPostEvent(winUnder->GetEventHandler(), event2);
540 }
541 }
542 break;
543
544 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
545 case wxHT_WINDOW_HORZ_SCROLLBAR:
546 sbar = win->GetScrollbar(wxHORIZONTAL);
547 break;
548
549 case wxHT_WINDOW_VERT_SCROLLBAR:
550 sbar = win->GetScrollbar(wxVERTICAL);
551 break;
552 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
553
554 default:
555 // forgot to update the switch after adding a new hit test code?
556 wxFAIL_MSG( wxT("unexpected HitTest() return value") );
557 // fall through
558
559 case wxHT_WINDOW_CORNER:
560 // don't actually know if this one is good for anything, but let it
561 // pass just in case
562
563 case wxHT_WINDOW_INSIDE:
564 // let the normal processing take place
565 event.Skip();
566 break;
567 }
568
569 #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
570 if ( sbar )
571 {
572 // translate the event coordinates to the scrollbar ones
573 pos = sbar->ScreenToClient(win->ClientToScreen(pos));
574
575 // and give the event to it
576 wxMouseEvent event2 = event;
577 event2.m_x = pos.x;
578 event2.m_y = pos.y;
579
580 (void)sbar->GetEventHandler()->ProcessEvent(event2);
581 }
582 #endif // __WXUNIVERSAL__ && wxUSE_SCROLLBAR
583 }
584
585 // ----------------------------------------------------------------------------
586 // wxPopupFocusHandler
587 // ----------------------------------------------------------------------------
588
589 void wxPopupFocusHandler::OnKillFocus(wxFocusEvent& event)
590 {
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();
594 while ( win )
595 {
596 if ( win == m_popup )
597 return;
598 win = win->GetParent();
599 }
600
601 m_popup->DismissAndNotify();
602 }
603
604 void wxPopupFocusHandler::OnChar(wxKeyEvent& event)
605 {
606 // we can be associated with the popup itself in which case we should avoid
607 // infinite recursion
608 static int s_inside;
609 wxRecursionGuard guard(s_inside);
610 if ( guard.IsInside() )
611 {
612 event.Skip();
613 return;
614 }
615
616 // let the window have it first, it might process the keys
617 if ( !m_popup->GetEventHandler()->ProcessEvent(event) )
618 {
619 // by default, dismiss the popup
620 m_popup->DismissAndNotify();
621 }
622 }
623
624 #endif // wxUSE_POPUPWIN