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