]> git.saurik.com Git - wxWidgets.git/blob - src/generic/mdig.cpp
fixing reentrancy which happened in tests, bringing client coordinates origins in...
[wxWidgets.git] / src / generic / mdig.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/mdig.cpp
3 // Purpose: Generic MDI (Multiple Document Interface) classes
4 // Author: Hans Van Leemputten
5 // Modified by: 2008-10-31 Vadim Zeitlin: derive from the base classes
6 // Created: 29/07/2002
7 // RCS-ID: $Id$
8 // Copyright: (c) 2002 Hans Van Leemputten
9 // (c) 2008 Vadim Zeitlin
10 // Licence: wxWindows licence
11 /////////////////////////////////////////////////////////////////////////////
12
13 // ===========================================================================
14 // declarations
15 // ===========================================================================
16
17 // ---------------------------------------------------------------------------
18 // headers
19 // ---------------------------------------------------------------------------
20
21 // For compilers that support precompilation, includes "wx.h".
22 #include "wx/wxprec.h"
23
24 #ifdef __BORLANDC__
25 #pragma hdrstop
26 #endif
27
28 #if wxUSE_MDI
29
30 #ifndef WX_PRECOMP
31 #include "wx/menu.h"
32 #include "wx/intl.h"
33 #include "wx/log.h"
34 #endif //WX_PRECOMP
35
36 #include "wx/mdi.h"
37 #include "wx/generic/mdig.h"
38 #include "wx/notebook.h"
39 #include "wx/scopeguard.h"
40
41 #include "wx/stockitem.h"
42
43 enum MDI_MENU_ID
44 {
45 wxWINDOWCLOSE = 4001,
46 wxWINDOWCLOSEALL,
47 wxWINDOWNEXT,
48 wxWINDOWPREV
49 };
50
51 //-----------------------------------------------------------------------------
52 // wxGenericMDIParentFrame
53 //-----------------------------------------------------------------------------
54
55 IMPLEMENT_DYNAMIC_CLASS(wxGenericMDIParentFrame, wxFrame)
56
57 BEGIN_EVENT_TABLE(wxGenericMDIParentFrame, wxFrame)
58 EVT_CLOSE(wxGenericMDIParentFrame::OnClose)
59 #if wxUSE_MENUS
60 EVT_MENU(wxID_ANY, wxGenericMDIParentFrame::OnWindowMenu)
61 #endif
62 END_EVENT_TABLE()
63
64 void wxGenericMDIParentFrame::Init()
65 {
66 #if wxUSE_MENUS
67 m_pMyMenuBar = NULL;
68 #endif // wxUSE_MENUS
69 }
70
71 wxGenericMDIParentFrame::~wxGenericMDIParentFrame()
72 {
73 // Make sure the client window is destructed before the menu bars are!
74 wxDELETE(m_clientWindow);
75
76 #if wxUSE_MENUS
77 wxDELETE(m_pMyMenuBar);
78
79 RemoveWindowMenu(GetMenuBar());
80 #endif // wxUSE_MENUS
81 }
82
83 bool wxGenericMDIParentFrame::Create(wxWindow *parent,
84 wxWindowID id,
85 const wxString& title,
86 const wxPoint& pos,
87 const wxSize& size,
88 long style,
89 const wxString& name)
90 {
91 // this style can be used to prevent a window from having the standard MDI
92 // "Window" menu
93 if ( !(style & wxFRAME_NO_WINDOW_MENU) )
94 {
95 #if wxUSE_MENUS
96 m_windowMenu = new wxMenu;
97
98 m_windowMenu->Append(wxWINDOWCLOSE, _("Cl&ose"));
99 m_windowMenu->Append(wxWINDOWCLOSEALL, _("Close All"));
100 m_windowMenu->AppendSeparator();
101 m_windowMenu->Append(wxWINDOWNEXT, _("&Next"));
102 m_windowMenu->Append(wxWINDOWPREV, _("&Previous"));
103 #endif // wxUSE_MENUS
104 }
105
106 // the scrolling styles don't make sense neither for us nor for our client
107 // window (to which they're supposed to apply)
108 style &= ~(wxHSCROLL | wxVSCROLL);
109
110 if ( !wxFrame::Create( parent, id, title, pos, size, style, name ) )
111 return false;
112
113 wxGenericMDIClientWindow * const client = OnCreateGenericClient();
114 if ( !client->CreateGenericClient(this) )
115 return false;
116
117 m_clientWindow = client;
118
119 return true;
120 }
121
122 wxGenericMDIClientWindow *wxGenericMDIParentFrame::OnCreateGenericClient()
123 {
124 return new wxGenericMDIClientWindow;
125 }
126
127 bool wxGenericMDIParentFrame::CloseAll()
128 {
129 wxGenericMDIClientWindow * const client = GetGenericClientWindow();
130 if ( !client )
131 return true; // none of the windows left
132
133 wxBookCtrlBase * const book = client->GetBookCtrl();
134 while ( book->GetPageCount() )
135 {
136 wxGenericMDIChildFrame * const child = client->GetChild(0);
137 if ( !child->Close() )
138 {
139 // it refused to close, don't close the remaining ones neither
140 return false;
141 }
142 }
143
144 return true;
145 }
146
147 #if wxUSE_MENUS
148 void wxGenericMDIParentFrame::SetWindowMenu(wxMenu* pMenu)
149 {
150 // Replace the window menu from the currently loaded menu bar.
151 wxMenuBar *pMenuBar = GetMenuBar();
152
153 if (m_windowMenu)
154 {
155 RemoveWindowMenu(pMenuBar);
156
157 wxDELETE(m_windowMenu);
158 }
159
160 if (pMenu)
161 {
162 m_windowMenu = pMenu;
163
164 AddWindowMenu(pMenuBar);
165 }
166 }
167
168 void wxGenericMDIParentFrame::SetMenuBar(wxMenuBar *pMenuBar)
169 {
170 // Remove the Window menu from the old menu bar
171 RemoveWindowMenu(GetMenuBar());
172 // Add the Window menu to the new menu bar.
173 AddWindowMenu(pMenuBar);
174
175 wxFrame::SetMenuBar(pMenuBar);
176 }
177 #endif // wxUSE_MENUS
178
179 void wxGenericMDIParentFrame::WXSetChildMenuBar(wxGenericMDIChildFrame *pChild)
180 {
181 #if wxUSE_MENUS
182 if (pChild == NULL)
183 {
184 // No Child, set Our menu bar back.
185 SetMenuBar(m_pMyMenuBar);
186
187 // Make sure we know our menu bar is in use
188 m_pMyMenuBar = NULL;
189 }
190 else
191 {
192 if (pChild->GetMenuBar() == NULL)
193 return;
194
195 // Do we need to save the current bar?
196 if (m_pMyMenuBar == NULL)
197 m_pMyMenuBar = GetMenuBar();
198
199 SetMenuBar(pChild->GetMenuBar());
200 }
201 #endif // wxUSE_MENUS
202 }
203
204 wxGenericMDIClientWindow *
205 wxGenericMDIParentFrame::GetGenericClientWindow() const
206 {
207 return static_cast<wxGenericMDIClientWindow *>(m_clientWindow);
208 }
209
210 wxBookCtrlBase *wxGenericMDIParentFrame::GetBookCtrl() const
211 {
212 wxGenericMDIClientWindow * const client = GetGenericClientWindow();
213 return client ? client->GetBookCtrl() : NULL;
214 }
215
216 void wxGenericMDIParentFrame::AdvanceActive(bool forward)
217 {
218 wxBookCtrlBase * const book = GetBookCtrl();
219 if ( book )
220 book->AdvanceSelection(forward);
221 }
222
223 void wxGenericMDIParentFrame::WXUpdateChildTitle(wxGenericMDIChildFrame *child)
224 {
225 wxGenericMDIClientWindow * const client = GetGenericClientWindow();
226
227 const int pos = client->FindChild(child);
228 if ( pos == wxNOT_FOUND )
229 return;
230
231 client->GetBookCtrl()->SetPageText(pos, child->GetTitle());
232 }
233
234 void wxGenericMDIParentFrame::WXActivateChild(wxGenericMDIChildFrame *child)
235 {
236 wxGenericMDIClientWindow * const client = GetGenericClientWindow();
237
238 const int pos = client->FindChild(child);
239 if ( pos == wxNOT_FOUND )
240 return;
241
242 client->GetBookCtrl()->SetSelection(pos);
243 }
244
245 void wxGenericMDIParentFrame::WXRemoveChild(wxGenericMDIChildFrame *child)
246 {
247 const bool removingActive = WXIsActiveChild(child);
248 if ( removingActive )
249 {
250 SetActiveChild(NULL);
251 WXSetChildMenuBar(NULL);
252 }
253
254 wxGenericMDIClientWindow * const client = GetGenericClientWindow();
255 wxCHECK_RET( client, "should have client window" );
256
257 wxBookCtrlBase * const book = client->GetBookCtrl();
258
259 // Remove page if still there
260 int pos = client->FindChild(child);
261 if ( pos != wxNOT_FOUND )
262 {
263 if ( book->RemovePage(pos) )
264 book->Refresh();
265 }
266
267 if ( removingActive )
268 {
269 // Set the new selection to a remaining page
270 const size_t count = book->GetPageCount();
271 if ( count > (size_t)pos )
272 {
273 book->SetSelection(pos);
274 }
275 else
276 {
277 if ( count > 0 )
278 book->SetSelection(count - 1);
279 }
280 }
281 }
282
283 bool
284 wxGenericMDIParentFrame::WXIsActiveChild(wxGenericMDIChildFrame *child) const
285 {
286 return static_cast<wxMDIChildFrameBase *>(GetActiveChild()) == child;
287 }
288
289 #if wxUSE_MENUS
290 void wxGenericMDIParentFrame::RemoveWindowMenu(wxMenuBar *pMenuBar)
291 {
292 if (pMenuBar && m_windowMenu)
293 {
294 // Remove old window menu
295 int pos = pMenuBar->FindMenu(_("&Window"));
296 if (pos != wxNOT_FOUND)
297 {
298 wxASSERT(m_windowMenu == pMenuBar->GetMenu(pos)); // DBG:: We're going to delete the wrong menu!!!
299 pMenuBar->Remove(pos);
300 }
301 }
302 }
303
304 void wxGenericMDIParentFrame::AddWindowMenu(wxMenuBar *pMenuBar)
305 {
306 if (pMenuBar && m_windowMenu)
307 {
308 int pos = pMenuBar->FindMenu(wxGetStockLabel(wxID_HELP,false));
309 if (pos == wxNOT_FOUND)
310 {
311 pMenuBar->Append(m_windowMenu, _("&Window"));
312 }
313 else
314 {
315 pMenuBar->Insert(pos, m_windowMenu, _("&Window"));
316 }
317 }
318 }
319
320 void wxGenericMDIParentFrame::OnWindowMenu(wxCommandEvent &event)
321 {
322 switch ( event.GetId() )
323 {
324 case wxWINDOWCLOSE:
325 if ( m_currentChild )
326 m_currentChild->Close();
327 break;
328
329 case wxWINDOWCLOSEALL:
330 CloseAll();
331 break;
332
333 case wxWINDOWNEXT:
334 ActivateNext();
335 break;
336
337 case wxWINDOWPREV:
338 ActivatePrevious();
339 break;
340
341 default:
342 event.Skip();
343 }
344 }
345 #endif // wxUSE_MENUS
346
347 void wxGenericMDIParentFrame::OnClose(wxCloseEvent& event)
348 {
349 if ( !CloseAll() )
350 event.Veto();
351 else
352 event.Skip();
353 }
354
355 bool wxGenericMDIParentFrame::ProcessEvent(wxEvent& event)
356 {
357 if ( m_currentChild )
358 {
359 // the menu events should be given to the child as we show its menu bar
360 // as our own
361 const wxEventType eventType = event.GetEventType();
362 if ( eventType == wxEVT_MENU ||
363 eventType == wxEVT_UPDATE_UI )
364 {
365 // set the flag indicating that this event was forwarded to the
366 // child from the parent and so shouldn't be propagated upwards if
367 // not processed to avoid infinite loop
368 m_childHandler = m_currentChild;
369 wxON_BLOCK_EXIT_NULL(m_childHandler);
370
371 if ( m_currentChild->ProcessWindowEvent(event) )
372 return true;
373 }
374 }
375
376 return wxMDIParentFrameBase::ProcessEvent(event);
377 }
378
379 // ----------------------------------------------------------------------------
380 // wxGenericMDIChildFrame
381 // ----------------------------------------------------------------------------
382
383 IMPLEMENT_DYNAMIC_CLASS(wxGenericMDIChildFrame, wxFrame)
384
385 BEGIN_EVENT_TABLE(wxGenericMDIChildFrame, wxFrame)
386 EVT_MENU_HIGHLIGHT_ALL(wxGenericMDIChildFrame::OnMenuHighlight)
387
388 EVT_CLOSE(wxGenericMDIChildFrame::OnClose)
389 END_EVENT_TABLE()
390
391 void wxGenericMDIChildFrame::Init()
392 {
393 #if wxUSE_MENUS
394 m_pMenuBar = NULL;
395 #endif // wxUSE_MENUS
396
397 #if !wxUSE_GENERIC_MDI_AS_NATIVE
398 m_mdiParentGeneric = NULL;
399 #endif
400 }
401
402 wxGenericMDIChildFrame::~wxGenericMDIChildFrame()
403 {
404 wxGenericMDIParentFrame * const parent = GetGenericMDIParent();
405
406 // it could happen that we don't have a valid parent if we hadn't been ever
407 // really created -- but in this case there is nothing else to do neither
408 if ( parent )
409 parent->WXRemoveChild(this);
410
411 #if wxUSE_MENUS
412 delete m_pMenuBar;
413 #endif // wxUSE_MENUS
414 }
415
416 bool wxGenericMDIChildFrame::Create(wxGenericMDIParentFrame *parent,
417 wxWindowID id,
418 const wxString& title,
419 const wxPoint& WXUNUSED(pos),
420 const wxSize& size,
421 long WXUNUSED(style),
422 const wxString& name)
423 {
424 // unfortunately we can't use the base class m_mdiParent field unless
425 // wxGenericMDIParentFrame is wxMDIParentFrame
426 #if wxUSE_GENERIC_MDI_AS_NATIVE
427 m_mdiParent = parent;
428 #else // generic != native
429 // leave m_mdiParent NULL, we don't have it
430 m_mdiParentGeneric = parent;
431 #endif
432
433 wxBookCtrlBase * const book = parent->GetBookCtrl();
434
435 wxASSERT_MSG( book, "Missing MDI client window." );
436
437 // note that we ignore the styles, none of the usual TLW styles apply to
438 // this (child) window
439 if ( !wxWindow::Create(book, id, wxDefaultPosition, size, 0, name) )
440 return false;
441
442 m_title = title;
443 book->AddPage(this, title, true);
444
445 return true;
446 }
447
448 #if wxUSE_MENUS
449 void wxGenericMDIChildFrame::SetMenuBar( wxMenuBar *menu_bar )
450 {
451 wxMenuBar *pOldMenuBar = m_pMenuBar;
452 m_pMenuBar = menu_bar;
453
454 if (m_pMenuBar)
455 {
456 wxGenericMDIParentFrame *parent = GetGenericMDIParent();
457
458 if ( parent )
459 {
460 m_pMenuBar->SetParent(parent);
461
462 if ( parent->WXIsActiveChild(this) )
463 {
464 // Replace current menu bars
465 if (pOldMenuBar)
466 parent->WXSetChildMenuBar(NULL);
467 parent->WXSetChildMenuBar(this);
468 }
469 }
470 }
471 }
472
473 wxMenuBar *wxGenericMDIChildFrame::GetMenuBar() const
474 {
475 return m_pMenuBar;
476 }
477 #endif // wxUSE_MENUS
478
479 void wxGenericMDIChildFrame::SetTitle(const wxString& title)
480 {
481 m_title = title;
482
483 wxGenericMDIParentFrame * const parent = GetGenericMDIParent();
484 if ( parent )
485 parent->WXUpdateChildTitle(this);
486 //else: it's ok, we might be not created yet
487 }
488
489 void wxGenericMDIChildFrame::Activate()
490 {
491 wxGenericMDIParentFrame * const parent = GetGenericMDIParent();
492
493 wxCHECK_RET( parent, "can't activate MDI child without parent" );
494 parent->WXActivateChild(this);
495 }
496
497 void wxGenericMDIChildFrame::OnMenuHighlight(wxMenuEvent& event)
498 {
499 wxGenericMDIParentFrame * const parent = GetGenericMDIParent();
500 if ( parent)
501 {
502 // we don't have any help text for this item,
503 // but may be the MDI frame does?
504 parent->OnMenuHighlight(event);
505 }
506 }
507
508 void wxGenericMDIChildFrame::OnClose(wxCloseEvent& WXUNUSED(event))
509 {
510 // we're not a TLW so don't delay the destruction of this window
511 delete this;
512 }
513
514 bool wxGenericMDIChildFrame::TryAfter(wxEvent& event)
515 {
516 // we shouldn't propagate the event to the parent if we received it from it
517 // in the first place
518 wxGenericMDIParentFrame * const parent = GetGenericMDIParent();
519 if ( parent && parent->WXIsInsideChildHandler(this) )
520 return false;
521
522 return wxTDIChildFrame::TryAfter(event);
523 }
524
525 // ----------------------------------------------------------------------------
526 // wxGenericMDIClientWindow
527 // ----------------------------------------------------------------------------
528
529 IMPLEMENT_DYNAMIC_CLASS(wxGenericMDIClientWindow, wxWindow)
530
531 bool
532 wxGenericMDIClientWindow::CreateGenericClient(wxWindow *parent)
533 {
534 if ( !wxWindow::Create(parent, wxID_ANY) )
535 return false;
536
537 m_notebook = new wxNotebook(this, wxID_ANY);
538 m_notebook->Connect
539 (
540 wxEVT_NOTEBOOK_PAGE_CHANGED,
541 wxNotebookEventHandler(
542 wxGenericMDIClientWindow::OnPageChanged),
543 NULL,
544 this
545 );
546
547 // now that we have a notebook to resize, hook up OnSize() too
548 Connect(wxEVT_SIZE, wxSizeEventHandler(wxGenericMDIClientWindow::OnSize));
549
550 return true;
551 }
552
553 wxBookCtrlBase *wxGenericMDIClientWindow::GetBookCtrl() const
554 {
555 return m_notebook;
556 }
557
558 wxGenericMDIChildFrame *wxGenericMDIClientWindow::GetChild(size_t pos) const
559 {
560 return static_cast<wxGenericMDIChildFrame *>(GetBookCtrl()->GetPage(pos));
561 }
562
563 int wxGenericMDIClientWindow::FindChild(wxGenericMDIChildFrame *child) const
564 {
565 wxBookCtrlBase * const book = GetBookCtrl();
566 const size_t count = book->GetPageCount();
567 for ( size_t pos = 0; pos < count; pos++ )
568 {
569 if ( book->GetPage(pos) == child )
570 return pos;
571 }
572
573 return wxNOT_FOUND;
574 }
575
576 void wxGenericMDIClientWindow::PageChanged(int oldSelection, int newSelection)
577 {
578 // Don't do anything if nothing changed
579 if (oldSelection == newSelection)
580 return;
581
582 // Again check if we really need to do this...
583 if (newSelection != -1)
584 {
585 wxGenericMDIChildFrame * const child = GetChild(newSelection);
586
587 if ( child->GetGenericMDIParent()->WXIsActiveChild(child) )
588 return;
589 }
590
591 // Notify old active child that it has been deactivated
592 if (oldSelection != -1)
593 {
594 wxGenericMDIChildFrame * const oldChild = GetChild(oldSelection);
595 if (oldChild)
596 {
597 wxActivateEvent event(wxEVT_ACTIVATE, false, oldChild->GetId());
598 event.SetEventObject( oldChild );
599 oldChild->GetEventHandler()->ProcessEvent(event);
600 }
601 }
602
603 // Notify new active child that it has been activated
604 if (newSelection != -1)
605 {
606 wxGenericMDIChildFrame * const activeChild = GetChild(newSelection);
607 if ( activeChild )
608 {
609 wxActivateEvent event(wxEVT_ACTIVATE, true, activeChild->GetId());
610 event.SetEventObject( activeChild );
611 activeChild->GetEventHandler()->ProcessEvent(event);
612
613 wxGenericMDIParentFrame * const
614 parent = activeChild->GetGenericMDIParent();
615
616 if ( parent )
617 {
618 // this is a dirty hack as activeChild is not really a
619 // wxMDIChildFrame at all but we still want to store it in the
620 // base class m_currentChild field and this will work as long
621 // as we only use as wxMDIChildFrameBase pointer (which it is)
622 parent->SetActiveChild(
623 reinterpret_cast<wxMDIChildFrame *>(activeChild));
624 parent->WXSetChildMenuBar(activeChild);
625 }
626 }
627 }
628 }
629
630 void wxGenericMDIClientWindow::OnPageChanged(wxBookCtrlEvent& event)
631 {
632 PageChanged(event.GetOldSelection(), event.GetSelection());
633
634 event.Skip();
635 }
636
637 void wxGenericMDIClientWindow::OnSize(wxSizeEvent& WXUNUSED(event))
638 {
639 m_notebook->SetSize(GetClientSize());
640 }
641
642 #endif // wxUSE_MDI
643