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