Lots of things to make menus and submenus work.
[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)
80 {
81 m_popup = popup;
82
83 #ifdef __WXGTK__
84 // ignore the next few OnKillFocus() calls
85 m_creationTime = time(NULL);
86 #endif // __WXGTK__
87 }
88
89 protected:
90 // event handlers
91 void OnKillFocus(wxFocusEvent& event);
92 void OnKeyDown(wxKeyEvent& event);
93
94 private:
95 wxPopupTransientWindow *m_popup;
96
97 // hack around wxGTK bug: we always get several kill focus events
98 // immediately after creation!
99 #ifdef __WXGTK__
100 time_t m_creationTime;
101 #endif // __WXGTK__
102
103 DECLARE_EVENT_TABLE()
104 };
105
106 // ----------------------------------------------------------------------------
107 // event tables
108 // ----------------------------------------------------------------------------
109
110 BEGIN_EVENT_TABLE(wxPopupWindowHandler, wxEvtHandler)
111 EVT_LEFT_DOWN(wxPopupWindowHandler::OnLeftDown)
112 END_EVENT_TABLE()
113
114 BEGIN_EVENT_TABLE(wxPopupFocusHandler, wxEvtHandler)
115 EVT_KILL_FOCUS(wxPopupFocusHandler::OnKillFocus)
116 EVT_KEY_DOWN(wxPopupFocusHandler::OnKeyDown)
117 END_EVENT_TABLE()
118
119 // ============================================================================
120 // implementation
121 // ============================================================================
122
123 // ----------------------------------------------------------------------------
124 // wxPopupWindowBase
125 // ----------------------------------------------------------------------------
126
127 wxPopupWindowBase::~wxPopupWindowBase()
128 {
129 // this destructor is required for Darwin
130 }
131
132 bool wxPopupWindowBase::Create(wxWindow* WXUNUSED(parent), int WXUNUSED(flags))
133 {
134 return TRUE;
135 }
136
137 void wxPopupWindowBase::Position(const wxPoint& ptOrigin,
138 const wxSize& size)
139 {
140 wxSize sizeScreen = wxGetDisplaySize(),
141 sizeSelf = GetSize();
142
143 // is there enough space to put the popup below the window (where we put it
144 // by default)?
145 wxCoord y = ptOrigin.y + size.y;
146 if ( y + sizeSelf.y > sizeScreen.y )
147 {
148 // check if there is enough space above
149 if ( ptOrigin.y > sizeSelf.y )
150 {
151 // do position the control above the window
152 y -= size.y + sizeSelf.y;
153 }
154 //else: not enough space below nor above, leave below
155 }
156
157 // now check left/right too
158 wxCoord x = ptOrigin.x + size.x;
159 if ( x + sizeSelf.x > sizeScreen.x )
160 {
161 // check if there is enough space to the left
162 if ( ptOrigin.x > sizeSelf.x )
163 {
164 // do position the control to the left
165 x -= size.x + sizeSelf.x;
166 }
167 //else: not enough space there neither, leave in default position
168 }
169
170 Move(x, y, wxSIZE_NO_ADJUSTMENTS);
171 }
172
173 // ----------------------------------------------------------------------------
174 // wxPopupTransientWindow
175 // ----------------------------------------------------------------------------
176
177 void wxPopupTransientWindow::Init()
178 {
179 m_child =
180 m_focus = (wxWindow *)NULL;
181
182 m_handlerFocus = NULL;
183 m_handlerPopup = NULL;
184 }
185
186 wxPopupTransientWindow::wxPopupTransientWindow(wxWindow *parent, int style)
187 {
188 Init();
189
190 (void)Create(parent, style);
191 }
192
193 wxPopupTransientWindow::~wxPopupTransientWindow()
194 {
195 PopHandlers();
196
197 delete m_handlerFocus;
198 delete m_handlerPopup;
199 }
200
201 void wxPopupTransientWindow::PopHandlers()
202 {
203 if ( m_child )
204 {
205 if ( !m_child->RemoveEventHandler(m_handlerPopup) )
206 {
207 // something is very wrong and someone else probably deleted our
208 // handler - so don't risk deleting it second time
209 m_handlerPopup = NULL;
210 }
211
212 m_child->ReleaseMouse();
213 m_child = NULL;
214 }
215
216 if ( m_focus )
217 {
218 if ( !m_focus->RemoveEventHandler(m_handlerFocus) )
219 {
220 // see above
221 m_handlerFocus = NULL;
222 }
223
224 m_focus = NULL;
225 }
226 }
227
228 void wxPopupTransientWindow::Popup(wxWindow *winFocus)
229 {
230 const wxWindowList& children = GetChildren();
231 if ( children.GetCount() )
232 {
233 m_child = children.GetFirst()->GetData();
234 }
235 else
236 {
237 m_child = this;
238 }
239
240 // we can't capture mouse before the window is shown in wxGTK, so do it
241 // first
242 Show();
243
244 delete m_handlerPopup;
245 m_handlerPopup = new wxPopupWindowHandler(this);
246
247 m_child->CaptureMouse();
248 m_child->PushEventHandler(m_handlerPopup);
249
250 m_focus = winFocus ? winFocus : this;
251 m_focus->SetFocus();
252
253 #ifdef __WXMSW__
254 // MSW doesn't allow to set focus to the popup window, but we need to
255 // subclass the window which has the focus, and not winFocus passed in or
256 // otherwise everything else breaks down
257 m_focus = FindFocus();
258 if ( m_focus )
259 #endif // __WXMSW__
260 {
261 delete m_handlerFocus;
262 m_handlerFocus = new wxPopupFocusHandler(this);
263
264 m_focus->PushEventHandler(m_handlerFocus);
265 }
266 }
267
268 void wxPopupTransientWindow::Dismiss()
269 {
270 PopHandlers();
271
272 Hide();
273 }
274
275 void wxPopupTransientWindow::DismissAndNotify()
276 {
277 Dismiss();
278
279 OnDismiss();
280 }
281
282 void wxPopupTransientWindow::OnDismiss()
283 {
284 // nothing to do here - but it may be interesting for derived class
285 }
286
287 bool wxPopupTransientWindow::ProcessLeftDown(wxMouseEvent& WXUNUSED(event))
288 {
289 // no special processing here
290 return FALSE;
291 }
292
293 #if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
294
295 // ----------------------------------------------------------------------------
296 // wxPopupComboWindow
297 // ----------------------------------------------------------------------------
298
299 wxPopupComboWindow::wxPopupComboWindow(wxComboControl *parent)
300 : wxPopupTransientWindow(parent)
301 {
302 m_combo = parent;
303 }
304
305 bool wxPopupComboWindow::Create(wxComboControl *parent)
306 {
307 m_combo = parent;
308
309 return wxPopupWindow::Create(parent);
310 }
311
312 void wxPopupComboWindow::PositionNearCombo()
313 {
314 // the origin point must be in screen coords
315 wxPoint ptOrigin = m_combo->ClientToScreen(wxPoint(0, 0));
316
317 #if 0 //def __WXUNIVERSAL__
318 // account for the fact that (0, 0) is not the top left corner of the
319 // window: there is also the border
320 wxRect rectBorders = m_combo->GetRenderer()->
321 GetBorderDimensions(m_combo->GetBorder());
322 ptOrigin.x -= rectBorders.x;
323 ptOrigin.y -= rectBorders.y;
324 #endif // __WXUNIVERSAL__
325
326 // position below or above the combobox: the width is 0 to put it exactly
327 // below us, not to the left or to the right
328 Position(ptOrigin, wxSize(0, m_combo->GetSize().y));
329 }
330
331 void wxPopupComboWindow::OnDismiss()
332 {
333 m_combo->OnDismiss();
334 }
335
336 #endif // wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
337
338 // ----------------------------------------------------------------------------
339 // wxPopupWindowHandler
340 // ----------------------------------------------------------------------------
341
342 void wxPopupWindowHandler::OnLeftDown(wxMouseEvent& event)
343 {
344 // let the window have it first (we're the first event handler in the chain
345 // of handlers for this window)
346 if ( m_popup->ProcessLeftDown(event) )
347 {
348 return;
349 }
350
351 wxPoint pos = event.GetPosition();
352
353 // scrollbar on which the click occured
354 wxWindow *sbar = NULL;
355
356 wxWindow *win = (wxWindow *)event.GetEventObject();
357
358 switch ( win->HitTest(pos.x, pos.y) )
359 {
360 case wxHT_WINDOW_OUTSIDE:
361 // clicking outside a popup dismisses it
362 m_popup->DismissAndNotify();
363 break;
364
365 #ifdef __WXUNIVERSAL__
366 case wxHT_WINDOW_HORZ_SCROLLBAR:
367 sbar = win->GetScrollbar(wxHORIZONTAL);
368 break;
369
370 case wxHT_WINDOW_VERT_SCROLLBAR:
371 sbar = win->GetScrollbar(wxVERTICAL);
372 break;
373 #endif
374
375 default:
376 // forgot to update the switch after adding a new hit test code?
377 wxFAIL_MSG( _T("unexpected HitTest() return value") );
378 // fall through
379
380 case wxHT_WINDOW_CORNER:
381 // don't actually know if this one is good for anything, but let it
382 // pass just in case
383
384 case wxHT_WINDOW_INSIDE:
385 // let the normal processing take place
386 event.Skip();
387 break;
388 }
389
390 if ( sbar )
391 {
392 // translate the event coordinates to the scrollbar ones
393 pos = sbar->ScreenToClient(win->ClientToScreen(pos));
394
395 // and give the event to it
396 wxMouseEvent event2 = event;
397 event2.m_x = pos.x;
398 event2.m_y = pos.y;
399
400 (void)sbar->GetEventHandler()->ProcessEvent(event2);
401 }
402 }
403
404 // ----------------------------------------------------------------------------
405 // wxPopupFocusHandler
406 // ----------------------------------------------------------------------------
407
408 void wxPopupFocusHandler::OnKillFocus(wxFocusEvent& event)
409 {
410 #ifdef __WXGTK__
411 // ignore the next OnKillFocus() call
412 if ( time(NULL) < m_creationTime + 1 )
413 {
414 event.Skip();
415
416 return;
417 }
418 #endif // __WXGTK__
419
420 // when we lose focus we always disappear - unless it goes to the popup (in
421 // which case we don't really lose it)
422 wxWindow *win = event.GetWindow();
423 while ( win )
424 {
425 if ( win == m_popup )
426 return;
427 win = win->GetParent();
428 }
429
430 m_popup->DismissAndNotify();
431 }
432
433 void wxPopupFocusHandler::OnKeyDown(wxKeyEvent& event)
434 {
435 // let the window have it first, it might process the keys
436 if ( !m_popup->ProcessEvent(event) )
437 {
438 // by default, dismiss the popup
439 m_popup->DismissAndNotify();
440 }
441 }
442
443 #endif // wxUSE_POPUPWIN
444