1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/mdi.cpp
3 // Purpose:
4 // Author:      Robert Roebling
5 // Copyright:   (c) 1998 Robert Roebling
6 // Licence:     wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
8 
9 // For compilers that support precompilation, includes "wx.h".
10 #include "wx/wxprec.h"
11 
12 #if wxUSE_MDI
13 
14 #include "wx/mdi.h"
15 
16 #ifndef WX_PRECOMP
17     #include "wx/intl.h"
18     #include "wx/menu.h"
19 #endif
20 
21 #include "wx/gtk/private.h"
22 
23 //-----------------------------------------------------------------------------
24 // "switch_page"
25 //-----------------------------------------------------------------------------
26 
27 extern "C" {
28 static void
switch_page(GtkNotebook * widget,GtkNotebookPage *,guint page_num,wxMDIParentFrame * parent)29 switch_page(GtkNotebook* widget, GtkNotebookPage*, guint page_num, wxMDIParentFrame* parent)
30 {
31     // send deactivate event to old child
32 
33     wxMDIChildFrame *child = parent->GetActiveChild();
34     if (child)
35     {
36         wxActivateEvent event1( wxEVT_ACTIVATE, false, child->GetId() );
37         event1.SetEventObject( child);
38         child->HandleWindowEvent( event1 );
39     }
40 
41     // send activate event to new child
42 
43     wxMDIClientWindowBase *client_window = parent->GetClientWindow();
44     if ( !client_window )
45         return;
46 
47     child = NULL;
48     GtkWidget* page = gtk_notebook_get_nth_page(widget, page_num);
49 
50     wxWindowList::compatibility_iterator node = client_window->GetChildren().GetFirst();
51     while ( node )
52     {
53         wxMDIChildFrame *child_frame = wxDynamicCast( node->GetData(), wxMDIChildFrame );
54 
55         // child_frame can be NULL when this is called from dtor, probably
56         // because g_signal_connect (m_widget, "switch_page", (see below)
57         // isn't deleted early enough
58         if (child_frame && child_frame->m_widget == page)
59         {
60             child = child_frame;
61             break;
62         }
63         node = node->GetNext();
64     }
65 
66     if (!child)
67          return;
68 
69     wxActivateEvent event2( wxEVT_ACTIVATE, true, child->GetId() );
70     event2.SetEventObject( child);
71     child->HandleWindowEvent( event2 );
72 }
73 }
74 
75 //-----------------------------------------------------------------------------
76 // wxMDIParentFrame
77 //-----------------------------------------------------------------------------
78 
79 wxIMPLEMENT_DYNAMIC_CLASS(wxMDIParentFrame, wxFrame);
80 
Init()81 void wxMDIParentFrame::Init()
82 {
83     m_justInserted = false;
84 }
85 
Create(wxWindow * parent,wxWindowID id,const wxString & title,const wxPoint & pos,const wxSize & size,long style,const wxString & name)86 bool wxMDIParentFrame::Create(wxWindow *parent,
87                               wxWindowID id,
88                               const wxString& title,
89                               const wxPoint& pos,
90                               const wxSize& size,
91                               long style,
92                               const wxString& name )
93 {
94     if ( !wxFrame::Create( parent, id, title, pos, size, style, name ) )
95         return false;
96 
97     m_clientWindow = OnCreateClient();
98     if ( !m_clientWindow->CreateClient(this, GetWindowStyleFlag()) )
99         return false;
100 
101     return true;
102 }
103 
OnInternalIdle()104 void wxMDIParentFrame::OnInternalIdle()
105 {
106     /* if a MDI child window has just been inserted
107        it has to be brought to the top in idle time. we
108        simply set the last notebook page active as new
109        pages can only be appended at the end */
110 
111     if (m_justInserted)
112     {
113         GtkNotebook *notebook = GTK_NOTEBOOK(m_clientWindow->m_widget);
114         gtk_notebook_set_current_page(notebook, -1);
115 
116         /* need to set the menubar of the child */
117         wxMDIChildFrame *active_child_frame = GetActiveChild();
118         if (active_child_frame != NULL)
119         {
120             wxMenuBar *menu_bar = active_child_frame->m_menuBar;
121             if (menu_bar)
122             {
123                 menu_bar->Attach(active_child_frame);
124             }
125         }
126         m_justInserted = false;
127         return;
128     }
129 
130     wxFrame::OnInternalIdle();
131 
132     wxMDIChildFrame *active_child_frame = GetActiveChild();
133     bool visible_child_menu = false;
134 
135     wxWindowList::compatibility_iterator node = m_clientWindow->GetChildren().GetFirst();
136     while (node)
137     {
138         wxMDIChildFrame *child_frame = wxDynamicCast( node->GetData(), wxMDIChildFrame );
139 
140         if ( child_frame )
141         {
142             wxMenuBar *menu_bar = child_frame->m_menuBar;
143             if ( menu_bar )
144             {
145                 if (child_frame == active_child_frame)
146                 {
147                     if (menu_bar->Show(true))
148                     {
149                         // Attach() asserts if we call it for an already
150                         // attached menu bar so don't do it if we're already
151                         // associated with this frame (it would be nice to get
152                         // rid of this check and ensure that this doesn't
153                         // happen...)
154                         if ( menu_bar->GetFrame() != child_frame )
155                             menu_bar->Attach( child_frame );
156                     }
157                     visible_child_menu = true;
158                 }
159                 else
160                 {
161                     if (menu_bar->Show(false))
162                     {
163                         menu_bar->Detach();
164                     }
165                 }
166             }
167         }
168 
169         node = node->GetNext();
170     }
171 
172     /* show/hide parent menu bar as required */
173     if ((m_frameMenuBar) &&
174         (m_frameMenuBar->IsShown() == visible_child_menu))
175     {
176         if (visible_child_menu)
177         {
178             m_frameMenuBar->Show( false );
179             m_frameMenuBar->Detach();
180         }
181         else
182         {
183             m_frameMenuBar->Show( true );
184             m_frameMenuBar->Attach( this );
185         }
186     }
187 }
188 
DoGetClientSize(int * width,int * height) const189 void wxMDIParentFrame::DoGetClientSize(int* width, int* height) const
190 {
191     wxFrame::DoGetClientSize(width, height);
192 
193     if (!m_useCachedClientSize && height)
194     {
195         wxMDIChildFrame* active_child_frame = GetActiveChild();
196         if (active_child_frame)
197         {
198             wxMenuBar* menubar = active_child_frame->m_menuBar;
199             if (menubar && menubar->IsShown())
200             {
201                 GtkRequisition req;
202                 gtk_widget_get_preferred_height(menubar->m_widget, NULL, &req.height);
203                 *height -= req.height;
204                 if (*height < 0) *height = 0;
205             }
206         }
207     }
208 }
209 
GetActiveChild() const210 wxMDIChildFrame *wxMDIParentFrame::GetActiveChild() const
211 {
212     if (!m_clientWindow) return NULL;
213 
214     GtkNotebook *notebook = GTK_NOTEBOOK(m_clientWindow->m_widget);
215     if (!notebook) return NULL;
216 
217     gint i = gtk_notebook_get_current_page( notebook );
218     if (i < 0) return NULL;
219 
220     GtkWidget* page = gtk_notebook_get_nth_page(notebook, i);
221     if (!page) return NULL;
222 
223     wxWindowList::compatibility_iterator node = m_clientWindow->GetChildren().GetFirst();
224     while (node)
225     {
226         if ( wxPendingDelete.Member(node->GetData()) )
227             return NULL;
228 
229         wxMDIChildFrame *child_frame = wxDynamicCast( node->GetData(), wxMDIChildFrame );
230 
231         if (!child_frame)
232             return NULL;
233 
234         if (child_frame->m_widget == page)
235             return child_frame;
236 
237         node = node->GetNext();
238     }
239 
240     return NULL;
241 }
242 
ActivateNext()243 void wxMDIParentFrame::ActivateNext()
244 {
245     if (m_clientWindow)
246       gtk_notebook_next_page( GTK_NOTEBOOK(m_clientWindow->m_widget) );
247 }
248 
ActivatePrevious()249 void wxMDIParentFrame::ActivatePrevious()
250 {
251     if (m_clientWindow)
252       gtk_notebook_prev_page( GTK_NOTEBOOK(m_clientWindow->m_widget) );
253 }
254 
255 //-----------------------------------------------------------------------------
256 // wxMDIChildFrame
257 //-----------------------------------------------------------------------------
258 
259 wxIMPLEMENT_DYNAMIC_CLASS(wxMDIChildFrame, wxFrame);
260 
wxBEGIN_EVENT_TABLE(wxMDIChildFrame,wxFrame)261 wxBEGIN_EVENT_TABLE(wxMDIChildFrame, wxFrame)
262     EVT_ACTIVATE(wxMDIChildFrame::OnActivate)
263     EVT_MENU_HIGHLIGHT_ALL(wxMDIChildFrame::OnMenuHighlight)
264 wxEND_EVENT_TABLE()
265 
266 void wxMDIChildFrame::Init()
267 {
268     m_menuBar = NULL;
269 }
270 
Create(wxMDIParentFrame * parent,wxWindowID id,const wxString & title,const wxPoint & WXUNUSED (pos),const wxSize & size,long style,const wxString & name)271 bool wxMDIChildFrame::Create(wxMDIParentFrame *parent,
272                              wxWindowID id,
273                              const wxString& title,
274                              const wxPoint& WXUNUSED(pos),
275                              const wxSize& size,
276                              long style,
277                              const wxString& name)
278 {
279     m_mdiParent = parent;
280     m_title = title;
281 
282     return wxWindow::Create(parent->GetClientWindow(), id,
283                             wxDefaultPosition, size,
284                             style, name);
285 }
286 
~wxMDIChildFrame()287 wxMDIChildFrame::~wxMDIChildFrame()
288 {
289     delete m_menuBar;
290 
291     // wxMDIClientWindow does not get redrawn properly after last child is removed
292     if (m_parent && m_parent->GetChildren().size() <= 1)
293         gtk_widget_queue_draw(m_parent->m_widget);
294 }
295 
GTKHandleRealized()296 void wxMDIChildFrame::GTKHandleRealized()
297 {
298     // since m_widget is not a GtkWindow, must bypass wxTopLevelWindowGTK
299     wxTopLevelWindowBase::GTKHandleRealized();
300 }
301 
SetMenuBar(wxMenuBar * menu_bar)302 void wxMDIChildFrame::SetMenuBar( wxMenuBar *menu_bar )
303 {
304     wxASSERT_MSG( m_menuBar == NULL, "Only one menubar allowed" );
305 
306     m_menuBar = menu_bar;
307 
308     if (m_menuBar)
309     {
310         wxMDIParentFrame *mdi_frame = (wxMDIParentFrame*)m_parent->GetParent();
311 
312         m_menuBar->SetParent( mdi_frame );
313 
314         /* insert the invisible menu bar into the _parent_ mdi frame */
315         m_menuBar->Show(false);
316         gtk_box_pack_start(GTK_BOX(mdi_frame->m_mainWidget), m_menuBar->m_widget, false, false, 0);
317         gtk_box_reorder_child(GTK_BOX(mdi_frame->m_mainWidget), m_menuBar->m_widget, 0);
318         gtk_widget_set_size_request(m_menuBar->m_widget, -1, -1);
319     }
320 }
321 
GetMenuBar() const322 wxMenuBar *wxMDIChildFrame::GetMenuBar() const
323 {
324     return m_menuBar;
325 }
326 
GTKGetNotebook() const327 GtkNotebook *wxMDIChildFrame::GTKGetNotebook() const
328 {
329     wxMDIClientWindow * const
330         client = wxStaticCast(GetParent(), wxMDIClientWindow);
331     wxCHECK( client, NULL );
332 
333     return GTK_NOTEBOOK(client->m_widget);
334 }
335 
Activate()336 void wxMDIChildFrame::Activate()
337 {
338     GtkNotebook * const notebook = GTKGetNotebook();
339     wxCHECK_RET( notebook, "no parent notebook?" );
340 
341     gint pageno = gtk_notebook_page_num( notebook, m_widget );
342     gtk_notebook_set_current_page( notebook, pageno );
343 }
344 
OnActivate(wxActivateEvent & WXUNUSED (event))345 void wxMDIChildFrame::OnActivate( wxActivateEvent& WXUNUSED(event) )
346 {
347 }
348 
OnMenuHighlight(wxMenuEvent & event)349 void wxMDIChildFrame::OnMenuHighlight( wxMenuEvent& event )
350 {
351 #if wxUSE_STATUSBAR
352     wxMDIParentFrame *mdi_frame = (wxMDIParentFrame*)m_parent->GetParent();
353     if ( !ShowMenuHelp(event.GetMenuId()) )
354     {
355         // we don't have any help text for this item, but may be the MDI frame
356         // does?
357         mdi_frame->OnMenuHighlight(event);
358     }
359 #endif // wxUSE_STATUSBAR
360 }
361 
SetTitle(const wxString & title)362 void wxMDIChildFrame::SetTitle( const wxString &title )
363 {
364     if ( title == m_title )
365         return;
366 
367     m_title = title;
368 
369     GtkNotebook * const notebook = GTKGetNotebook();
370     wxCHECK_RET( notebook, "no parent notebook?" );
371     gtk_notebook_set_tab_label_text(notebook, m_widget, wxGTK_CONV( title ) );
372 }
373 
DoGetPosition(int * x,int * y) const374 void wxMDIChildFrame::DoGetPosition(int *x, int *y) const
375 {
376     // Pages of notebook always have position (0, 0) in its client area, so
377     // override this method to return this instead of the actual offset from
378     // the parent top left corner that the base class version returns.
379     if ( x )
380         *x = 0;
381     if ( y )
382         *y = 0;
383 }
384 
385 //-----------------------------------------------------------------------------
386 // wxMDIClientWindow
387 //-----------------------------------------------------------------------------
388 
389 wxIMPLEMENT_DYNAMIC_CLASS(wxMDIClientWindow, wxWindow);
390 
~wxMDIClientWindow()391 wxMDIClientWindow::~wxMDIClientWindow()
392 {
393     // disconnect our handler because our ~wxWindow (which is going to be called
394     // after this dtor) will call DestroyChildren(); in turns our children
395     // ~wxWindow dtors will call wxWindow::Show(false) and this will generate
396     // a call to gtk_mdi_page_change_callback with an invalid parent
397     // (because gtk_mdi_page_change_callback expects a wxMDIClientWindow but
398     //  at that point of the dtor chain we are a simple wxWindow!)
399     g_signal_handlers_disconnect_by_func(m_widget, (void*)switch_page, GetParent());
400 }
401 
CreateClient(wxMDIParentFrame * parent,long style)402 bool wxMDIClientWindow::CreateClient(wxMDIParentFrame *parent, long style)
403 {
404     if ( !PreCreation( parent, wxDefaultPosition, wxDefaultSize ) ||
405          !CreateBase( parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
406                        style, wxDefaultValidator, "wxMDIClientWindow" ))
407     {
408         wxFAIL_MSG( "wxMDIClientWindow creation failed" );
409         return false;
410     }
411 
412     m_widget = gtk_notebook_new();
413     g_object_ref(m_widget);
414 
415     g_signal_connect(m_widget, "switch_page", G_CALLBACK(switch_page), parent);
416 
417     gtk_notebook_set_scrollable( GTK_NOTEBOOK(m_widget), 1 );
418 
419     m_parent->DoAddChild( this );
420 
421     PostCreation();
422 
423     Show( true );
424 
425     return true;
426 }
427 
AddChildGTK(wxWindowGTK * child)428 void wxMDIClientWindow::AddChildGTK(wxWindowGTK* child)
429 {
430     wxMDIChildFrame* child_frame = static_cast<wxMDIChildFrame*>(child);
431     wxString s = child_frame->GetTitle();
432     if ( s.empty() )
433         s = _("MDI child");
434 
435     GtkWidget *label_widget = gtk_label_new( s.mbc_str() );
436 #ifdef __WXGTK4__
437     g_object_set(label_widget, "xalign", 0.0f, NULL);
438 #else
439     wxGCC_WARNING_SUPPRESS(deprecated-declarations)
440     gtk_misc_set_alignment( GTK_MISC(label_widget), 0.0, 0.5 );
441     wxGCC_WARNING_RESTORE()
442 #endif
443 
444     GtkNotebook* notebook = GTK_NOTEBOOK(m_widget);
445 
446     gtk_notebook_append_page( notebook, child->m_widget, label_widget );
447 
448     wxMDIParentFrame* parent_frame = static_cast<wxMDIParentFrame*>(GetParent());
449     parent_frame->m_justInserted = true;
450 }
451 
452 #endif // wxUSE_MDI
453