]>
Commit | Line | Data |
---|---|---|
42b0d8b9 VS |
1 | ///////////////////////////////////////////////////////////////////////////// |
2 | // Name: src/dfb/nonownedwnd.cpp | |
fd2be5df | 3 | // Purpose: implementation of wxNonOwnedWindow |
42b0d8b9 VS |
4 | // Author: Vaclav Slavik |
5 | // Created: 2006-12-24 | |
42b0d8b9 VS |
6 | // Copyright: (c) 2006 REA Elektronik GmbH |
7 | // Licence: wxWindows licence | |
8 | ///////////////////////////////////////////////////////////////////////////// | |
9 | ||
10 | // For compilers that support precompilation, includes "wx.h". | |
11 | #include "wx/wxprec.h" | |
12 | ||
13 | #include "wx/toplevel.h" | |
14 | ||
15 | #ifndef WX_PRECOMP | |
16 | #include "wx/app.h" | |
17 | #endif // WX_PRECOMP | |
18 | ||
19 | #include "wx/hashmap.h" | |
20 | #include "wx/evtloop.h" | |
21 | #include "wx/dfb/private.h" | |
22 | ||
a5001e93 VS |
23 | #define TRACE_EVENTS "events" |
24 | #define TRACE_PAINT "paint" | |
42b0d8b9 VS |
25 | |
26 | // ============================================================================ | |
27 | // globals | |
28 | // ============================================================================ | |
29 | ||
30 | // mapping of DirectFB windows to wxTLWs: | |
31 | WX_DECLARE_HASH_MAP(DFBWindowID, wxNonOwnedWindow*, | |
32 | wxIntegerHash, wxIntegerEqual, | |
33 | wxDfbWindowsMap); | |
34 | static wxDfbWindowsMap gs_dfbWindowsMap; | |
35 | ||
36 | // ============================================================================ | |
37 | // helpers | |
38 | // ============================================================================ | |
39 | ||
40 | // Queue of paint requests | |
41 | class wxDfbQueuedPaintRequests | |
42 | { | |
43 | public: | |
44 | ~wxDfbQueuedPaintRequests() { Clear(); } | |
45 | ||
46 | // Adds paint request to the queue | |
47 | void Add(const wxRect& rect) | |
48 | { | |
49 | // We use a simple implementation here for now: all refresh requests | |
50 | // are merged together into single rectangle that is superset of | |
51 | // all the requested rectangles. This wastes some blitting and painting | |
52 | // time, but OTOH, EVT_PAINT handler is called only once per window. | |
53 | m_invalidated.Union(rect); | |
54 | } | |
55 | ||
56 | // Is the queue empty? | |
57 | bool IsEmpty() const { return m_invalidated.IsEmpty(); } | |
58 | ||
59 | // Empties the queue | |
60 | void Clear() { m_invalidated = wxRect(); } | |
61 | ||
62 | // Gets the next request in the queue, returns true if there was one, | |
63 | // false if the queue was empty | |
64 | bool GetNext(wxRect& rect) | |
65 | { | |
66 | if ( m_invalidated.IsEmpty() ) | |
67 | return false; | |
68 | ||
69 | rect = m_invalidated; | |
70 | Clear(); // there's only one item in the queue | |
71 | return true; | |
72 | } | |
73 | ||
74 | private: | |
75 | // currently invalidated region | |
76 | wxRect m_invalidated; | |
77 | }; | |
78 | ||
79 | // ============================================================================ | |
80 | // wxNonOwnedWindow | |
81 | // ============================================================================ | |
82 | ||
83 | // ---------------------------------------------------------------------------- | |
84 | // creation & destruction | |
85 | // ---------------------------------------------------------------------------- | |
86 | ||
87 | void wxNonOwnedWindow::Init() | |
88 | { | |
89 | m_isShown = false; | |
90 | m_sizeSet = false; | |
91 | m_opacity = 255; | |
92 | m_toPaint = new wxDfbQueuedPaintRequests; | |
93 | m_isPainting = false; | |
94 | } | |
95 | ||
96 | bool wxNonOwnedWindow::Create(wxWindow *parent, | |
97 | wxWindowID id, | |
98 | const wxPoint& pos, | |
99 | const wxSize& size, | |
100 | long style, | |
101 | const wxString &name) | |
102 | { | |
a5001e93 VS |
103 | wxCHECK_MSG( pos.x >= 0 && pos.y >= 0, false, "invalid position" ); |
104 | wxCHECK_MSG( size.x > 0 && size.y > 0, false, "invalid size" ); | |
42b0d8b9 VS |
105 | |
106 | m_tlw = this; | |
107 | ||
108 | // create DirectFB window: | |
109 | wxIDirectFBDisplayLayerPtr layer(wxIDirectFB::Get()->GetDisplayLayer()); | |
a5001e93 | 110 | wxCHECK_MSG( layer, false, "no display layer" ); |
42b0d8b9 VS |
111 | |
112 | DFBWindowDescription desc; | |
113 | desc.flags = (DFBWindowDescriptionFlags) | |
114 | (DWDESC_CAPS | | |
115 | DWDESC_WIDTH | DWDESC_HEIGHT | DWDESC_POSX | DWDESC_POSY); | |
116 | desc.caps = DWCAPS_DOUBLEBUFFER; | |
117 | desc.posx = pos.x; | |
118 | desc.posy = pos.y; | |
119 | desc.width = size.x; | |
120 | desc.height = size.y; | |
121 | m_dfbwin = layer->CreateWindow(&desc); | |
122 | if ( !m_dfbwin ) | |
123 | return false; | |
124 | ||
125 | // add the new TLW to DFBWindowID->wxTLW map: | |
126 | DFBWindowID winid; | |
127 | if ( !m_dfbwin->GetID(&winid) ) | |
128 | return false; | |
129 | gs_dfbWindowsMap[winid] = this; | |
130 | ||
131 | // TLWs are created initially hidden: | |
132 | if ( !m_dfbwin->SetOpacity(wxALPHA_TRANSPARENT) ) | |
133 | return false; | |
134 | ||
135 | if ( !wxWindow::Create(NULL, id, pos, size, style, name) ) | |
136 | return false; | |
137 | ||
138 | SetParent(parent); | |
139 | if ( parent ) | |
140 | parent->AddChild(this); | |
141 | ||
142 | if ( style & (wxSTAY_ON_TOP | wxPOPUP_WINDOW) ) | |
143 | { | |
144 | m_dfbwin->SetStackingClass(DWSC_UPPER); | |
145 | } | |
146 | ||
147 | // direct events in this window to the global event buffer: | |
148 | m_dfbwin->AttachEventBuffer(wxEventLoop::GetDirectFBEventBuffer()); | |
149 | ||
150 | return true; | |
151 | } | |
152 | ||
153 | wxNonOwnedWindow::~wxNonOwnedWindow() | |
154 | { | |
c6212a0c | 155 | SendDestroyEvent(); |
42b0d8b9 VS |
156 | |
157 | // destroy all children before we destroy the underlying DirectFB window, | |
158 | // so that if any of them does something with the TLW, it will still work: | |
159 | DestroyChildren(); | |
160 | ||
161 | // it's safe to delete the underlying DirectFB window now: | |
162 | wxDELETE(m_toPaint); | |
163 | ||
164 | if ( !m_dfbwin ) | |
165 | return; | |
166 | ||
167 | // remove the TLW from DFBWindowID->wxTLW map: | |
168 | DFBWindowID winid; | |
169 | if ( m_dfbwin->GetID(&winid) ) | |
170 | gs_dfbWindowsMap.erase(winid); | |
171 | ||
172 | m_dfbwin->Destroy(); | |
173 | m_dfbwin.Reset(); | |
174 | } | |
175 | ||
176 | // ---------------------------------------------------------------------------- | |
177 | // window size & position | |
178 | // ---------------------------------------------------------------------------- | |
179 | ||
180 | void wxNonOwnedWindow::DoGetPosition(int *x, int *y) const | |
181 | { | |
182 | m_dfbwin->GetPosition(x, y); | |
183 | } | |
184 | ||
185 | void wxNonOwnedWindow::DoGetSize(int *width, int *height) const | |
186 | { | |
187 | m_dfbwin->GetSize(width, height); | |
188 | } | |
189 | ||
190 | void wxNonOwnedWindow::DoMoveWindow(int x, int y, int width, int height) | |
191 | { | |
192 | wxPoint curpos = GetPosition(); | |
193 | if ( curpos.x != x || curpos.y != y ) | |
194 | { | |
195 | m_dfbwin->MoveTo(x, y); | |
196 | } | |
197 | ||
198 | wxSize cursize = GetSize(); | |
199 | if ( cursize.x != width || cursize.y != height ) | |
200 | { | |
201 | // changing window's size changes its surface: | |
202 | InvalidateDfbSurface(); | |
203 | ||
204 | m_dfbwin->Resize(width, height); | |
205 | ||
206 | // we must repaint the window after it changed size: | |
207 | if ( IsShown() ) | |
208 | DoRefreshWindow(); | |
209 | } | |
210 | } | |
211 | ||
212 | // ---------------------------------------------------------------------------- | |
213 | // showing and hiding | |
214 | // ---------------------------------------------------------------------------- | |
215 | ||
216 | bool wxNonOwnedWindow::Show(bool show) | |
217 | { | |
218 | // NB: this calls wxWindow::Show() and so ensures DoRefreshWindow() is | |
219 | // called on the window -- we'll need that below | |
220 | if ( !wxWindow::Show(show) ) | |
221 | return false; | |
222 | ||
223 | // If this is the first time Show was called, send size event, | |
224 | // so that the frame can adjust itself (think auto layout or single child) | |
225 | if ( !m_sizeSet ) | |
226 | { | |
227 | m_sizeSet = true; | |
228 | wxSizeEvent event(GetSize(), GetId()); | |
229 | event.SetEventObject(this); | |
937013e0 | 230 | HandleWindowEvent(event); |
42b0d8b9 VS |
231 | } |
232 | ||
233 | // make sure the window is fully painted, with all pending updates, before | |
234 | // DFB WM shows it, otherwise it would attempt to show either empty (= | |
235 | // black) window surface (if shown for the first time) or it would show | |
236 | // window with outdated content; note that the window was already refreshed | |
237 | // in the wxWindow::Show() call above: | |
238 | if ( show ) | |
239 | Update(); | |
240 | ||
241 | // hide/show the window by setting its opacity to 0/full: | |
242 | m_dfbwin->SetOpacity(show ? m_opacity : 0); | |
243 | ||
244 | if ( show ) | |
fd2be5df VS |
245 | { |
246 | wxWindow *focused = FindFocus(); | |
247 | if ( focused && focused->GetTLW() == this ) | |
248 | { | |
249 | // focus is on this frame or its children, apply it to DirectFB | |
250 | SetDfbFocus(); | |
251 | } | |
252 | // else: don't do anything, if this is wxFrame or wxDialog that should | |
253 | // get focus when it's shown, | |
254 | // wxTopLevelWindowDFB::HandleFocusEvent() will do it as soon as | |
255 | // the event loop starts | |
256 | } | |
42b0d8b9 VS |
257 | |
258 | return true; | |
259 | } | |
260 | ||
84e45580 VS |
261 | void wxNonOwnedWindow::Raise() |
262 | { | |
263 | m_dfbwin->RaiseToTop(); | |
264 | } | |
265 | ||
266 | void wxNonOwnedWindow::Lower() | |
267 | { | |
268 | m_dfbwin->LowerToBottom(); | |
269 | } | |
270 | ||
42b0d8b9 VS |
271 | // ---------------------------------------------------------------------------- |
272 | // surfaces and painting | |
273 | // ---------------------------------------------------------------------------- | |
274 | ||
275 | wxIDirectFBSurfacePtr wxNonOwnedWindow::ObtainDfbSurface() const | |
276 | { | |
277 | return m_dfbwin->GetSurface(); | |
278 | } | |
279 | ||
280 | void wxNonOwnedWindow::HandleQueuedPaintRequests() | |
281 | { | |
282 | if ( m_toPaint->IsEmpty() ) | |
283 | return; // nothing to do | |
284 | ||
285 | if ( IsFrozen() || !IsShown() ) | |
286 | { | |
287 | // nothing to do if the window is frozen or hidden; clear the queue | |
288 | // and return (note that it's OK to clear the queue even if the window | |
289 | // is frozen, because Thaw() calls Refresh()): | |
290 | m_toPaint->Clear(); | |
291 | return; | |
292 | } | |
293 | ||
294 | // process queued paint requests: | |
295 | wxRect winRect(wxPoint(0, 0), GetSize()); | |
296 | wxRect paintedRect; | |
297 | ||
298 | // important note: all DCs created from now until m_isPainting is reset to | |
299 | // false will not update the front buffer as this flag indicates that we'll | |
300 | // blit the entire back buffer to front soon | |
301 | m_isPainting = true; | |
302 | ||
42b0d8b9 | 303 | int requestsCount = 0; |
42b0d8b9 VS |
304 | |
305 | wxRect request; | |
306 | while ( m_toPaint->GetNext(request) ) | |
307 | { | |
42b0d8b9 | 308 | requestsCount++; |
42b0d8b9 VS |
309 | wxRect clipped(request); |
310 | clipped.Intersect(winRect); | |
311 | if ( clipped.IsEmpty() ) | |
312 | continue; // nothing to refresh | |
313 | ||
314 | wxLogTrace(TRACE_PAINT, | |
a5001e93 | 315 | "%p ('%s'): processing paint request [%i,%i,%i,%i]", |
42b0d8b9 VS |
316 | this, GetName().c_str(), |
317 | clipped.x, clipped.y, clipped.GetRight(), clipped.GetBottom()); | |
318 | ||
319 | PaintWindow(clipped); | |
320 | ||
321 | // remember rectangle covering all repainted areas: | |
322 | if ( paintedRect.IsEmpty() ) | |
323 | paintedRect = clipped; | |
324 | else | |
325 | paintedRect.Union(clipped); | |
326 | } | |
327 | ||
328 | m_isPainting = false; | |
329 | ||
330 | m_toPaint->Clear(); | |
331 | ||
332 | if ( paintedRect.IsEmpty() ) | |
333 | return; // no painting occurred, no need to flip | |
334 | ||
335 | // Flip the surface to make the changes visible. Note that the rectangle we | |
336 | // flip is *superset* of the union of repainted rectangles (created as | |
337 | // "rectangles union" by wxRect::Union) and so some parts of the back | |
338 | // buffer that we didn't touch in this HandleQueuedPaintRequests call will | |
339 | // be copied to the front buffer as well. This is safe/correct thing to do | |
340 | // *only* because wx always use wxIDirectFBSurface::FlipToFront() and so | |
341 | // the back and front buffers contain the same data. | |
342 | // | |
343 | // Note that we do _not_ split m_toPaint into disjoint rectangles and | |
344 | // do FlipToFront() for each of them, because that could result in visible | |
345 | // updating of the screen; instead, we prefer to flip everything at once. | |
346 | ||
347 | DFBRegion r = {paintedRect.GetLeft(), paintedRect.GetTop(), | |
348 | paintedRect.GetRight(), paintedRect.GetBottom()}; | |
349 | DFBRegion *rptr = (winRect == paintedRect) ? NULL : &r; | |
350 | ||
351 | GetDfbSurface()->FlipToFront(rptr); | |
352 | ||
353 | wxLogTrace(TRACE_PAINT, | |
a5001e93 | 354 | "%p ('%s'): processed %i paint requests, flipped surface: [%i,%i,%i,%i]", |
42b0d8b9 VS |
355 | this, GetName().c_str(), |
356 | requestsCount, | |
357 | paintedRect.x, paintedRect.y, | |
358 | paintedRect.GetRight(), paintedRect.GetBottom()); | |
359 | } | |
360 | ||
361 | void wxNonOwnedWindow::DoRefreshRect(const wxRect& rect) | |
362 | { | |
363 | // don't overlap outside of the window (NB: 'rect' is in window coords): | |
364 | wxRect r(rect); | |
365 | r.Intersect(wxRect(GetSize())); | |
366 | if ( r.IsEmpty() ) | |
367 | return; | |
368 | ||
369 | wxLogTrace(TRACE_PAINT, | |
a5001e93 | 370 | "%p ('%s'): [TLW] refresh rect [%i,%i,%i,%i]", |
42b0d8b9 VS |
371 | this, GetName().c_str(), |
372 | rect.x, rect.y, rect.GetRight(), rect.GetBottom()); | |
373 | ||
374 | // defer painting until idle time or until Update() is called: | |
375 | m_toPaint->Add(rect); | |
376 | } | |
377 | ||
378 | void wxNonOwnedWindow::Update() | |
379 | { | |
380 | HandleQueuedPaintRequests(); | |
381 | } | |
382 | ||
383 | // --------------------------------------------------------------------------- | |
384 | // events handling | |
385 | // --------------------------------------------------------------------------- | |
386 | ||
fd2be5df VS |
387 | namespace |
388 | { | |
389 | ||
390 | static wxNonOwnedWindow *gs_insideDFBFocusHandlerOf = NULL; | |
391 | ||
392 | struct InsideDFBFocusHandlerSetter | |
393 | { | |
394 | InsideDFBFocusHandlerSetter(wxNonOwnedWindow *win) | |
395 | { | |
396 | wxASSERT( gs_insideDFBFocusHandlerOf == NULL ); | |
397 | gs_insideDFBFocusHandlerOf = win; | |
398 | } | |
399 | ~InsideDFBFocusHandlerSetter() | |
400 | { | |
401 | gs_insideDFBFocusHandlerOf = NULL; | |
402 | } | |
403 | }; | |
404 | ||
405 | } // anonymous namespace | |
406 | ||
407 | ||
42b0d8b9 VS |
408 | void wxNonOwnedWindow::SetDfbFocus() |
409 | { | |
a5001e93 | 410 | wxCHECK_RET( IsShown(), "cannot set focus to hidden window" ); |
42b0d8b9 | 411 | wxASSERT_MSG( FindFocus() && FindFocus()->GetTLW() == this, |
a5001e93 | 412 | "setting DirectFB focus to unexpected window" ); |
42b0d8b9 | 413 | |
fd2be5df VS |
414 | // Don't set DirectFB focus if we're called from HandleFocusEvent() on |
415 | // this window, because we already have the focus in that case. Not only | |
416 | // would it be unnecessary, it would be harmful: RequestFocus() adds | |
417 | // an event to DirectFB event queue and calling it when in | |
418 | // HandleFocusEvent() could result in a window being focused when it | |
419 | // should not be. Consider this example: | |
420 | // | |
421 | // tlw1->SetFocus(); // (1) | |
422 | // tlw2->SetFocus(); // (2) | |
423 | // | |
424 | // This results in adding these events to DFB queue: | |
425 | // | |
426 | // DWET_GOTFOCUS(tlw1) | |
427 | // DWET_LOSTFOCUS(tlw1) | |
428 | // DWET_GOTFOCUS(tlw2) | |
429 | // | |
430 | // Note that the events are processed by event loop, i.e. not between | |
431 | // execution of lines (1) and (2) above. So by the time the first | |
432 | // DWET_GOTFOCUS event is handled, tlw2->SetFocus() was already executed. | |
433 | // If we onconditionally called RequestFocus() from here, handling the | |
434 | // first event would result in this change to the event queue: | |
435 | // | |
436 | // DWET_LOSTFOCUS(tlw1) | |
437 | // DWET_GOTFOCUS(tlw2) // (3) | |
438 | // DWET_LOSTFOCUS(tlw2) | |
439 | // DWET_GOTFOCUS(tlw1) | |
440 | // | |
441 | // And the focus would get back to tlw1 even though that's not what we | |
442 | // wanted. | |
443 | ||
444 | if ( gs_insideDFBFocusHandlerOf == this ) | |
445 | return; | |
446 | ||
42b0d8b9 VS |
447 | GetDirectFBWindow()->RequestFocus(); |
448 | } | |
449 | ||
450 | /* static */ | |
451 | void wxNonOwnedWindow::HandleDFBWindowEvent(const wxDFBWindowEvent& event_) | |
452 | { | |
453 | const DFBWindowEvent& event = event_; | |
454 | ||
455 | if ( gs_dfbWindowsMap.find(event.window_id) == gs_dfbWindowsMap.end() ) | |
456 | { | |
457 | wxLogTrace(TRACE_EVENTS, | |
a5001e93 | 458 | "received event for unknown DirectFB window, ignoring"); |
42b0d8b9 VS |
459 | return; |
460 | } | |
461 | ||
462 | wxNonOwnedWindow *tlw = gs_dfbWindowsMap[event.window_id]; | |
42b0d8b9 VS |
463 | |
464 | switch ( event.type ) | |
465 | { | |
466 | case DWET_KEYDOWN: | |
467 | case DWET_KEYUP: | |
468 | { | |
6954a1e2 VS |
469 | wxWindow *recipient = wxWindow::FindFocus(); |
470 | if ( !recipient ) | |
471 | { | |
472 | wxLogTrace(TRACE_EVENTS, | |
a5001e93 | 473 | "ignoring event: no recipient window"); |
6954a1e2 VS |
474 | return; |
475 | } | |
476 | ||
477 | wxCHECK_RET( recipient && recipient->GetTLW() == tlw, | |
a5001e93 | 478 | "event recipient not in TLW which received the event" ); |
6954a1e2 VS |
479 | |
480 | recipient->HandleKeyEvent(event_); | |
42b0d8b9 VS |
481 | break; |
482 | } | |
483 | ||
6954a1e2 VS |
484 | case DWET_GOTFOCUS: |
485 | case DWET_LOSTFOCUS: | |
fd2be5df VS |
486 | { |
487 | InsideDFBFocusHandlerSetter inside(tlw); | |
488 | tlw->HandleFocusEvent(event_); | |
489 | } | |
6954a1e2 VS |
490 | break; |
491 | ||
42b0d8b9 VS |
492 | case DWET_NONE: |
493 | case DWET_ALL: | |
a5001e93 | 494 | wxFAIL_MSG( "invalid event type" ); |
42b0d8b9 | 495 | break; |
42b0d8b9 VS |
496 | |
497 | default: | |
498 | // we're not interested in them here | |
499 | break; | |
500 | } | |
42b0d8b9 VS |
501 | } |
502 | ||
503 | // --------------------------------------------------------------------------- | |
504 | // idle events processing | |
505 | // --------------------------------------------------------------------------- | |
506 | ||
507 | void wxNonOwnedWindow::OnInternalIdle() | |
508 | { | |
509 | wxWindow::OnInternalIdle(); | |
510 | HandleQueuedPaintRequests(); | |
511 | } |