| 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 | } |