dismiss the popup when an unprocessed key is pressed
[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 license
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
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 && !defined(__WXMOTIF__)
32
33 #include "wx/popupwin.h"
34
35 #ifndef WX_PRECOMP
36 #include "wx/combobox.h" // wxComboControl
37 #endif //WX_PRECOMP
38
39 #ifdef __WXUNIVERSAL__
40 #include "wx/univ/renderer.h"
41 #endif // __WXUNIVERSAL__
42
43 // there is no src/{msw,mgl}/popupwin.cpp to put this in, so we do it here - BTW we
44 // probably could do it for all ports here just as well
45 #if defined(__WXMSW__) || defined(__WXMGL__)
46 IMPLEMENT_DYNAMIC_CLASS(wxPopupWindow, wxWindow)
47 #endif // __WXMSW__
48
49 IMPLEMENT_DYNAMIC_CLASS(wxPopupTransientWindow, wxPopupWindow)
50
51 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
52 IMPLEMENT_DYNAMIC_CLASS(wxPopupComboWindow, wxPopupTransientWindow)
53 #endif
54
55 // ----------------------------------------------------------------------------
56 // private classes
57 // ----------------------------------------------------------------------------
58
59 // event handlers which we use to intercept events which cause the popup to
60 // disappear
61 class wxPopupWindowHandler : public wxEvtHandler
62 {
63 public:
64 wxPopupWindowHandler(wxPopupTransientWindow *popup) { m_popup = popup; }
65
66 protected:
67 // event handlers
68 void OnLeftDown(wxMouseEvent& event);
69
70 private:
71 wxPopupTransientWindow *m_popup;
72
73 DECLARE_EVENT_TABLE()
74 };
75
76 class wxPopupFocusHandler : public wxEvtHandler
77 {
78 public:
79 wxPopupFocusHandler(wxPopupTransientWindow *popup) { m_popup = popup; }
80
81 protected:
82 // event handlers
83 void OnKillFocus(wxFocusEvent& event);
84 void OnKeyUp(wxKeyEvent& event);
85
86 private:
87 wxPopupTransientWindow *m_popup;
88
89 DECLARE_EVENT_TABLE()
90 };
91
92 // ----------------------------------------------------------------------------
93 // event tables
94 // ----------------------------------------------------------------------------
95
96 BEGIN_EVENT_TABLE(wxPopupWindowHandler, wxEvtHandler)
97 EVT_LEFT_DOWN(wxPopupWindowHandler::OnLeftDown)
98 END_EVENT_TABLE()
99
100 BEGIN_EVENT_TABLE(wxPopupFocusHandler, wxEvtHandler)
101 EVT_KILL_FOCUS(wxPopupFocusHandler::OnKillFocus)
102 EVT_KEY_UP(wxPopupFocusHandler::OnKeyUp)
103 END_EVENT_TABLE()
104
105 // ============================================================================
106 // implementation
107 // ============================================================================
108
109 // ----------------------------------------------------------------------------
110 // wxPopupWindowBase
111 // ----------------------------------------------------------------------------
112
113 wxPopupWindowBase::~wxPopupWindowBase()
114 {
115 // this destructor is required for Darwin
116 }
117
118 bool wxPopupWindowBase::Create(wxWindow* WXUNUSED(parent), int WXUNUSED(flags))
119 {
120 return TRUE;
121 }
122
123 void wxPopupWindowBase::Position(const wxPoint& ptOrigin,
124 const wxSize& size)
125 {
126 wxSize sizeScreen = wxGetDisplaySize(),
127 sizeSelf = GetSize();
128
129 // is there enough space to put the popup below the window (where we put it
130 // by default)?
131 wxCoord y = ptOrigin.y + size.y;
132 if ( y + sizeSelf.y > sizeScreen.y )
133 {
134 // check if there is enough space above
135 if ( ptOrigin.y > sizeSelf.y )
136 {
137 // do position the control above the window
138 y -= size.y + sizeSelf.y;
139 }
140 //else: not enough space below nor above, leave below
141 }
142
143 // now check left/right too
144 wxCoord x = ptOrigin.x + size.x;
145 if ( x + sizeSelf.x > sizeScreen.x )
146 {
147 // check if there is enough space to the left
148 if ( ptOrigin.x > sizeSelf.x )
149 {
150 // do position the control to the left
151 x -= size.x + sizeSelf.x;
152 }
153 //else: not enough space there neither, leave in default position
154 }
155
156 Move(x, y, wxSIZE_NO_ADJUSTMENTS);
157 }
158
159 // ----------------------------------------------------------------------------
160 // wxPopupTransientWindow
161 // ----------------------------------------------------------------------------
162
163 void wxPopupTransientWindow::Init()
164 {
165 m_child =
166 m_focus = (wxWindow *)NULL;
167 }
168
169 wxPopupTransientWindow::wxPopupTransientWindow(wxWindow *parent, int style)
170 {
171 Init();
172
173 (void)Create(parent, style);
174 }
175
176 wxPopupTransientWindow::~wxPopupTransientWindow()
177 {
178 PopHandlers();
179 }
180
181 void wxPopupTransientWindow::PopHandlers()
182 {
183 if ( m_child )
184 {
185 m_child->PopEventHandler(TRUE /* delete it */);
186 m_child->ReleaseMouse();
187 m_child = NULL;
188 }
189
190 if ( m_focus )
191 {
192 m_focus->PopEventHandler(TRUE /* delete it */);
193 m_focus = NULL;
194 }
195 }
196
197 void wxPopupTransientWindow::Popup(wxWindow *winFocus)
198 {
199 const wxWindowList& children = GetChildren();
200 if ( children.GetCount() )
201 {
202 m_child = children.GetFirst()->GetData();
203 }
204 else
205 {
206 m_child = this;
207 }
208
209 // we can't capture mouse before the window is shown in wxGTK
210 #ifdef __WXGTK__
211 Show();
212 #endif
213
214 m_child->CaptureMouse();
215 m_child->PushEventHandler(new wxPopupWindowHandler(this));
216
217 #ifndef __WXGTK__
218 Show();
219 #endif
220
221 m_focus = winFocus ? winFocus : this;
222 m_focus->SetFocus();
223
224 // FIXME: I don't know why does this happen but sometimes SetFocus() simply
225 // refuses to work under MSW - no error happens but the focus is not
226 // given to the window, i.e. the assert below is triggered
227 //
228 // Try work around this as we can...
229 #if 0
230 wxASSERT_MSG( FindFocus() == m_focus, _T("setting focus failed") );
231 #else
232 m_focus = FindFocus();
233 #endif
234
235 if ( m_focus )
236 {
237 m_focus->PushEventHandler(new wxPopupFocusHandler(this));
238 }
239 }
240
241 void wxPopupTransientWindow::Dismiss()
242 {
243 PopHandlers();
244
245 Hide();
246 }
247
248 void wxPopupTransientWindow::DismissAndNotify()
249 {
250 Dismiss();
251
252 OnDismiss();
253 }
254
255 void wxPopupTransientWindow::OnDismiss()
256 {
257 // nothing to do here - but it may be interesting for derived class
258 }
259
260 bool wxPopupTransientWindow::ProcessLeftDown(wxMouseEvent& WXUNUSED(event))
261 {
262 // no special processing here
263 return FALSE;
264 }
265
266 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
267
268 // ----------------------------------------------------------------------------
269 // wxPopupComboWindow
270 // ----------------------------------------------------------------------------
271
272 wxPopupComboWindow::wxPopupComboWindow(wxComboControl *parent)
273 : wxPopupTransientWindow(parent)
274 {
275 m_combo = parent;
276 }
277
278 bool wxPopupComboWindow::Create(wxComboControl *parent)
279 {
280 m_combo = parent;
281
282 return wxPopupWindow::Create(parent);
283 }
284
285 void wxPopupComboWindow::PositionNearCombo()
286 {
287 // the origin point must be in screen coords
288 wxPoint ptOrigin = m_combo->ClientToScreen(wxPoint(0, 0));
289
290 #if 0 //def __WXUNIVERSAL__
291 // account for the fact that (0, 0) is not the top left corner of the
292 // window: there is also the border
293 wxRect rectBorders = m_combo->GetRenderer()->
294 GetBorderDimensions(m_combo->GetBorder());
295 ptOrigin.x -= rectBorders.x;
296 ptOrigin.y -= rectBorders.y;
297 #endif // __WXUNIVERSAL__
298
299 // position below or above the combobox: the width is 0 to put it exactly
300 // below us, not to the left or to the right
301 Position(ptOrigin, wxSize(0, m_combo->GetSize().y));
302 }
303
304 void wxPopupComboWindow::OnDismiss()
305 {
306 m_combo->OnDismiss();
307 }
308
309 #endif // wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
310
311 // ----------------------------------------------------------------------------
312 // wxPopupWindowHandler
313 // ----------------------------------------------------------------------------
314
315 void wxPopupWindowHandler::OnLeftDown(wxMouseEvent& event)
316 {
317 // let the window have it first (we're the first event handler in the chain
318 // of handlers for this window)
319 if ( m_popup->ProcessLeftDown(event) )
320 {
321 return;
322 }
323
324 wxPoint pos = event.GetPosition();
325
326 // scrollbar on which the click occured
327 wxWindow *sbar = NULL;
328
329 wxWindow *win = (wxWindow *)event.GetEventObject();
330 switch ( win->HitTest(pos.x, pos.y) )
331 {
332 case wxHT_WINDOW_OUTSIDE:
333 // clicking outside a popup dismisses it
334 m_popup->DismissAndNotify();
335 break;
336
337 #ifdef __WXUNIVERSAL__
338 case wxHT_WINDOW_HORZ_SCROLLBAR:
339 sbar = win->GetScrollbar(wxHORIZONTAL);
340 break;
341
342 case wxHT_WINDOW_VERT_SCROLLBAR:
343 sbar = win->GetScrollbar(wxVERTICAL);
344 break;
345 #endif
346
347 default:
348 // forgot to update the switch after adding a new hit test code?
349 wxFAIL_MSG( _T("unexpected HitTest() return value") );
350 // fall through
351
352 case wxHT_WINDOW_CORNER:
353 // don't actually know if this one is good for anything, but let it
354 // pass just in case
355
356 case wxHT_WINDOW_INSIDE:
357 // let the normal processing take place
358 event.Skip();
359 break;
360 }
361
362 if ( sbar )
363 {
364 // translate the event coordinates to the scrollbar ones
365 pos = sbar->ScreenToClient(win->ClientToScreen(pos));
366
367 // and give the event to it
368 wxMouseEvent event2 = event;
369 event2.m_x = pos.x;
370 event2.m_y = pos.y;
371
372 (void)sbar->GetEventHandler()->ProcessEvent(event2);
373 }
374 }
375
376 // ----------------------------------------------------------------------------
377 // wxPopupFocusHandler
378 // ----------------------------------------------------------------------------
379
380 void wxPopupFocusHandler::OnKillFocus(wxFocusEvent& event)
381 {
382 // when we lose focus we always disappear - unless it goes to the popup (in
383 // which case we don't really lose it)
384 if ( event.GetWindow() != m_popup )
385 m_popup->DismissAndNotify();
386 }
387
388 void wxPopupFocusHandler::OnKeyUp(wxKeyEvent& event)
389 {
390 // let the window have it first, it might process the keys
391 if ( !m_popup->ProcessEvent(event) )
392 {
393 // by default, dismiss the popup
394 m_popup->DismissAndNotify();
395 }
396 }
397
398 #endif // wxUSE_POPUPWIN
399