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