Remove all lines containing cvs/svn "$Id$" keyword.
[wxWidgets.git] / src / dfb / nonownedwnd.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/dfb/nonownedwnd.cpp
3 // Purpose: implementation of wxNonOwnedWindow
4 // Author: Vaclav Slavik
5 // Created: 2006-12-24
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
23 #define TRACE_EVENTS "events"
24 #define TRACE_PAINT "paint"
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 {
103 wxCHECK_MSG( pos.x >= 0 && pos.y >= 0, false, "invalid position" );
104 wxCHECK_MSG( size.x > 0 && size.y > 0, false, "invalid size" );
105
106 m_tlw = this;
107
108 // create DirectFB window:
109 wxIDirectFBDisplayLayerPtr layer(wxIDirectFB::Get()->GetDisplayLayer());
110 wxCHECK_MSG( layer, false, "no display layer" );
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 {
155 SendDestroyEvent();
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);
230 HandleWindowEvent(event);
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 )
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 }
257
258 return true;
259 }
260
261 void wxNonOwnedWindow::Raise()
262 {
263 m_dfbwin->RaiseToTop();
264 }
265
266 void wxNonOwnedWindow::Lower()
267 {
268 m_dfbwin->LowerToBottom();
269 }
270
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
303 int requestsCount = 0;
304
305 wxRect request;
306 while ( m_toPaint->GetNext(request) )
307 {
308 requestsCount++;
309 wxRect clipped(request);
310 clipped.Intersect(winRect);
311 if ( clipped.IsEmpty() )
312 continue; // nothing to refresh
313
314 wxLogTrace(TRACE_PAINT,
315 "%p ('%s'): processing paint request [%i,%i,%i,%i]",
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,
354 "%p ('%s'): processed %i paint requests, flipped surface: [%i,%i,%i,%i]",
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,
370 "%p ('%s'): [TLW] refresh rect [%i,%i,%i,%i]",
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
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
408 void wxNonOwnedWindow::SetDfbFocus()
409 {
410 wxCHECK_RET( IsShown(), "cannot set focus to hidden window" );
411 wxASSERT_MSG( FindFocus() && FindFocus()->GetTLW() == this,
412 "setting DirectFB focus to unexpected window" );
413
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
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,
458 "received event for unknown DirectFB window, ignoring");
459 return;
460 }
461
462 wxNonOwnedWindow *tlw = gs_dfbWindowsMap[event.window_id];
463
464 switch ( event.type )
465 {
466 case DWET_KEYDOWN:
467 case DWET_KEYUP:
468 {
469 wxWindow *recipient = wxWindow::FindFocus();
470 if ( !recipient )
471 {
472 wxLogTrace(TRACE_EVENTS,
473 "ignoring event: no recipient window");
474 return;
475 }
476
477 wxCHECK_RET( recipient && recipient->GetTLW() == tlw,
478 "event recipient not in TLW which received the event" );
479
480 recipient->HandleKeyEvent(event_);
481 break;
482 }
483
484 case DWET_GOTFOCUS:
485 case DWET_LOSTFOCUS:
486 {
487 InsideDFBFocusHandlerSetter inside(tlw);
488 tlw->HandleFocusEvent(event_);
489 }
490 break;
491
492 case DWET_NONE:
493 case DWET_ALL:
494 wxFAIL_MSG( "invalid event type" );
495 break;
496
497 default:
498 // we're not interested in them here
499 break;
500 }
501 }
502
503 // ---------------------------------------------------------------------------
504 // idle events processing
505 // ---------------------------------------------------------------------------
506
507 void wxNonOwnedWindow::OnInternalIdle()
508 {
509 wxWindow::OnInternalIdle();
510 HandleQueuedPaintRequests();
511 }