refactor WM_COMMAND messages handling in MDI frames to avoid duplicating code unneces...
[wxWidgets.git] / samples / mdi / mdi.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: mdi.cpp
3 // Purpose: MDI sample
4 // Author: Julian Smart
5 // Modified by: 2008-10-31 Vadim Zeitlin: big clean up
6 // Created: 04/01/98
7 // RCS-ID: $Id$
8 // Copyright: (c) 1997 Julian Smart
9 // (c) 2008 Vadim Zeitlin
10 // Licence: wxWindows license
11 /////////////////////////////////////////////////////////////////////////////
12
13 // ===========================================================================
14 // declarations
15 // ===========================================================================
16
17 // ---------------------------------------------------------------------------
18 // headers
19 // ---------------------------------------------------------------------------
20
21 // For compilers that support precompilation, includes "wx/wx.h".
22 #include "wx/wxprec.h"
23
24 #ifdef __BORLANDC__
25 #pragma hdrstop
26 #endif
27
28 #ifndef WX_PRECOMP
29 #include "wx/wx.h"
30 #include "wx/mdi.h"
31 #endif
32
33 #include "wx/toolbar.h"
34
35 #if !defined(__WXMSW__)
36 #include "../sample.xpm"
37 #include "chart.xpm"
38 #endif
39
40 #include "bitmaps/new.xpm"
41 #include "bitmaps/open.xpm"
42 #include "bitmaps/save.xpm"
43 #include "bitmaps/copy.xpm"
44 #include "bitmaps/cut.xpm"
45 #include "bitmaps/paste.xpm"
46 #include "bitmaps/print.xpm"
47 #include "bitmaps/help.xpm"
48
49 // replace this 0 with 1 to build the sample using the generic MDI classes (you
50 // may also need to add src/generic/mdig.cpp to the build)
51 #if 0
52 #include "wx/generic/mdig.h"
53 #define wxMDIParentFrame wxGenericMDIParentFrame
54 #define wxMDIChildFrame wxGenericMDIChildFrame
55 #define wxMDIClientWindow wxGenericMDIClientWindow
56 #endif
57
58 #include "mdi.h"
59
60 IMPLEMENT_APP(MyApp)
61
62 // ---------------------------------------------------------------------------
63 // event tables
64 // ---------------------------------------------------------------------------
65
66 BEGIN_EVENT_TABLE(MyFrame, wxMDIParentFrame)
67 EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
68 EVT_MENU(wxID_NEW, MyFrame::OnNewWindow)
69 EVT_MENU(MDI_FULLSCREEN, MyFrame::OnFullScreen)
70 EVT_MENU(wxID_EXIT, MyFrame::OnQuit)
71
72 EVT_MENU(wxID_CLOSE_ALL, MyFrame::OnCloseAll)
73
74 EVT_CLOSE(MyFrame::OnClose)
75 END_EVENT_TABLE()
76
77 // Note that wxID_NEW and wxID_ABOUT commands get passed
78 // to the parent window for processing, so no need to
79 // duplicate event handlers here.
80 BEGIN_EVENT_TABLE(MyChild, wxMDIChildFrame)
81 EVT_MENU(wxID_CLOSE, MyChild::OnClose)
82 EVT_MENU(MDI_REFRESH, MyChild::OnRefresh)
83 EVT_MENU(MDI_CHANGE_TITLE, MyChild::OnChangeTitle)
84 EVT_MENU(MDI_CHANGE_POSITION, MyChild::OnChangePosition)
85 EVT_MENU(MDI_CHANGE_SIZE, MyChild::OnChangeSize)
86
87 #if wxUSE_CLIPBOARD
88 EVT_MENU(wxID_PASTE, MyChild::OnPaste)
89 EVT_UPDATE_UI(wxID_PASTE, MyChild::OnUpdatePaste)
90 #endif // wxUSE_CLIPBOARD
91
92 EVT_SIZE(MyChild::OnSize)
93 EVT_MOVE(MyChild::OnMove)
94
95 EVT_CLOSE(MyChild::OnCloseWindow)
96 END_EVENT_TABLE()
97
98 BEGIN_EVENT_TABLE(MyCanvas, wxScrolledWindow)
99 EVT_MOUSE_EVENTS(MyCanvas::OnEvent)
100 END_EVENT_TABLE()
101
102 // ===========================================================================
103 // implementation
104 // ===========================================================================
105
106 // ---------------------------------------------------------------------------
107 // MyApp
108 // ---------------------------------------------------------------------------
109
110 // Initialise this in OnInit, not statically
111 bool MyApp::OnInit()
112 {
113 if ( !wxApp::OnInit() )
114 return false;
115
116 // Create the main frame window
117
118 MyFrame *frame = new MyFrame;
119
120 frame->Show(true);
121
122 return true;
123 }
124
125 // ---------------------------------------------------------------------------
126 // MyFrame
127 // ---------------------------------------------------------------------------
128
129 // Define my frame constructor
130 MyFrame::MyFrame()
131 : wxMDIParentFrame(NULL, wxID_ANY, "wxWidgets MDI Sample",
132 wxDefaultPosition, wxSize(500, 400))
133 {
134 SetIcon(wxICON(sample));
135
136 // Make a menubar
137 #if wxUSE_MENUS
138 wxMenu *file_menu = new wxMenu;
139
140 file_menu->Append(wxID_NEW, "&New window\tCtrl-N", "Create a new child window");
141 file_menu->AppendCheckItem(MDI_FULLSCREEN, "Show &fullscreen\tCtrl-F");
142 file_menu->Append(wxID_EXIT, "&Exit\tAlt-X", "Quit the program");
143
144 wxMenu *help_menu = new wxMenu;
145 help_menu->Append(wxID_ABOUT, "&About\tF1");
146
147 wxMenuBar *menu_bar = new wxMenuBar;
148
149 menu_bar->Append(file_menu, "&File");
150 menu_bar->Append(help_menu, "&Help");
151
152 // Associate the menu bar with the frame
153 SetMenuBar(menu_bar);
154
155
156 // This shows that the standard window menu may be customized:
157 wxMenu * const windowMenu = GetWindowMenu();
158 if ( windowMenu )
159 {
160 // we can change the labels of standard items (which also means we can
161 // set up accelerators for them as they're part of the label)
162 windowMenu->SetLabel(wxID_MDI_WINDOW_TILE_HORZ,
163 "&Tile horizontally\tCtrl-Shift-H");
164 windowMenu->SetLabel(wxID_MDI_WINDOW_TILE_VERT,
165 "&Tile vertically\tCtrl-Shift-V");
166
167 // we can also change the help string
168 windowMenu->SetHelpString(wxID_MDI_WINDOW_CASCADE,
169 "Arrange windows in cascade");
170
171 // we can remove some items
172 windowMenu->Delete(wxID_MDI_WINDOW_ARRANGE_ICONS);
173
174 // and we can add completely custom commands -- but then we must handle
175 // them ourselves, see OnCloseAll()
176 windowMenu->AppendSeparator();
177 windowMenu->Append(wxID_CLOSE_ALL, "&Close all windows\tCtrl-Shift-C",
178 "Close all open windows");
179
180 SetWindowMenu(windowMenu);
181 }
182 #endif // wxUSE_MENUS
183
184 #if wxUSE_STATUSBAR
185 CreateStatusBar();
186 #endif // wxUSE_STATUSBAR
187
188
189 m_textWindow = new wxTextCtrl(this, wxID_ANY, "A help window",
190 wxDefaultPosition, wxDefaultSize,
191 wxTE_MULTILINE | wxSUNKEN_BORDER);
192
193 #if wxUSE_TOOLBAR
194 CreateToolBar(wxNO_BORDER | wxTB_FLAT | wxTB_HORIZONTAL);
195 InitToolBar(GetToolBar());
196 #endif // wxUSE_TOOLBAR
197
198 #if wxUSE_ACCEL
199 // Accelerators
200 wxAcceleratorEntry entries[3];
201 entries[0].Set(wxACCEL_CTRL, (int) 'N', wxID_NEW);
202 entries[1].Set(wxACCEL_CTRL, (int) 'X', wxID_EXIT);
203 entries[2].Set(wxACCEL_CTRL, (int) 'A', wxID_ABOUT);
204 wxAcceleratorTable accel(3, entries);
205 SetAcceleratorTable(accel);
206 #endif // wxUSE_ACCEL
207
208 // connect it only now, after creating m_textWindow
209 Connect(wxEVT_SIZE, wxSizeEventHandler(MyFrame::OnSize));
210 }
211
212 MyFrame::~MyFrame()
213 {
214 // and disconnect it to prevent accessing already deleted m_textWindow in
215 // the size event handler if it's called during destruction
216 Disconnect(wxEVT_SIZE, wxSizeEventHandler(MyFrame::OnSize));
217 }
218
219 void MyFrame::OnClose(wxCloseEvent& event)
220 {
221 unsigned numChildren = MyChild::GetChildrenCount();
222 if ( event.CanVeto() && (numChildren > 0) )
223 {
224 wxString msg;
225 msg.Printf("%d windows still open, close anyhow?", numChildren);
226 if ( wxMessageBox(msg, "Please confirm",
227 wxICON_QUESTION | wxYES_NO) != wxYES )
228 {
229 event.Veto();
230
231 return;
232 }
233 }
234
235 event.Skip();
236 }
237
238 void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event))
239 {
240 Close();
241 }
242
243 void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) )
244 {
245 (void)wxMessageBox("wxWidgets 2.0 MDI Demo\n"
246 "Author: Julian Smart (c) 1997\n"
247 "Usage: mdi.exe", "About MDI Demo");
248 }
249
250 void MyFrame::OnNewWindow(wxCommandEvent& WXUNUSED(event) )
251 {
252 // create and show another child frame
253 MyChild *subframe = new MyChild(this);
254 subframe->Show(true);
255 }
256
257 void MyFrame::OnFullScreen(wxCommandEvent& event)
258 {
259 ShowFullScreen(event.IsChecked());
260 }
261
262 void MyFrame::OnCloseAll(wxCommandEvent& WXUNUSED(event))
263 {
264 for ( wxWindowList::const_iterator i = GetChildren().begin();
265 i != GetChildren().end();
266 ++i )
267 {
268 if ( wxDynamicCast(*i, wxMDIChildFrame) )
269 (*i)->Close();
270 }
271 }
272
273 void MyFrame::OnSize(wxSizeEvent& event)
274 {
275 int w, h;
276 GetClientSize(&w, &h);
277
278 m_textWindow->SetSize(0, 0, 200, h);
279 GetClientWindow()->SetSize(200, 0, w - 200, h);
280
281 // FIXME: On wxX11, we need the MDI frame to process this
282 // event, but on other platforms this should not
283 // be done.
284 #ifdef __WXUNIVERSAL__
285 event.Skip();
286 #else
287 wxUnusedVar(event);
288 #endif
289 }
290
291 #if wxUSE_TOOLBAR
292 void MyFrame::InitToolBar(wxToolBar* toolBar)
293 {
294 wxBitmap bitmaps[8];
295
296 bitmaps[0] = wxBitmap( new_xpm );
297 bitmaps[1] = wxBitmap( open_xpm );
298 bitmaps[2] = wxBitmap( save_xpm );
299 bitmaps[3] = wxBitmap( copy_xpm );
300 bitmaps[4] = wxBitmap( cut_xpm );
301 bitmaps[5] = wxBitmap( paste_xpm );
302 bitmaps[6] = wxBitmap( print_xpm );
303 bitmaps[7] = wxBitmap( help_xpm );
304
305 toolBar->AddTool(wxID_NEW, "New", bitmaps[0], "New file");
306 toolBar->AddTool(1, "Open", bitmaps[1], "Open file");
307 toolBar->AddTool(2, "Save", bitmaps[2], "Save file");
308 toolBar->AddSeparator();
309 toolBar->AddTool(3, "Copy", bitmaps[3], "Copy");
310 toolBar->AddTool(4, "Cut", bitmaps[4], "Cut");
311 toolBar->AddTool(5, "Paste", bitmaps[5], "Paste");
312 toolBar->AddSeparator();
313 toolBar->AddTool(6, "Print", bitmaps[6], "Print");
314 toolBar->AddSeparator();
315 toolBar->AddTool(wxID_ABOUT, "About", bitmaps[7], "Help");
316
317 toolBar->Realize();
318 }
319 #endif // wxUSE_TOOLBAR
320
321 // ---------------------------------------------------------------------------
322 // MyCanvas
323 // ---------------------------------------------------------------------------
324
325 // Define a constructor for my canvas
326 MyCanvas::MyCanvas(wxWindow *parent, const wxPoint& pos, const wxSize& size)
327 : wxScrolledWindow(parent, wxID_ANY, pos, size,
328 wxSUNKEN_BORDER |
329 wxNO_FULL_REPAINT_ON_RESIZE |
330 wxVSCROLL | wxHSCROLL)
331 {
332 SetBackgroundColour(wxColour("WHITE"));
333 SetCursor(wxCursor(wxCURSOR_PENCIL));
334
335 SetScrollbars(20, 20, 50, 50);
336
337 m_dirty = false;
338 }
339
340 // Define the repainting behaviour
341 void MyCanvas::OnDraw(wxDC& dc)
342 {
343 if ( !m_text.empty() )
344 dc.DrawText(m_text, 10, 10);
345
346 dc.SetFont(*wxSWISS_FONT);
347 dc.SetPen(*wxGREEN_PEN);
348 dc.DrawLine(0, 0, 200, 200);
349 dc.DrawLine(200, 0, 0, 200);
350
351 dc.SetBrush(*wxCYAN_BRUSH);
352 dc.SetPen(*wxRED_PEN);
353 dc.DrawRectangle(100, 100, 100, 50);
354 dc.DrawRoundedRectangle(150, 150, 100, 50, 20);
355
356 dc.DrawEllipse(250, 250, 100, 50);
357 #if wxUSE_SPLINES
358 dc.DrawSpline(50, 200, 50, 100, 200, 10);
359 #endif // wxUSE_SPLINES
360 dc.DrawLine(50, 230, 200, 230);
361 dc.DrawText("This is a test string", 50, 230);
362
363 wxPoint points[3];
364 points[0].x = 200; points[0].y = 300;
365 points[1].x = 100; points[1].y = 400;
366 points[2].x = 300; points[2].y = 400;
367
368 dc.DrawPolygon(3, points);
369 }
370
371 // This implements a tiny doodling program! Drag the mouse using the left
372 // button.
373 void MyCanvas::OnEvent(wxMouseEvent& event)
374 {
375 wxClientDC dc(this);
376 PrepareDC(dc);
377
378 wxPoint pt(event.GetLogicalPosition(dc));
379
380 static long xpos = -1;
381 static long ypos = -1;
382
383 if (xpos > -1 && ypos > -1 && event.Dragging())
384 {
385 dc.SetPen(*wxBLACK_PEN);
386 dc.DrawLine(xpos, ypos, pt.x, pt.y);
387
388 m_dirty = true;
389 }
390
391 xpos = pt.x;
392 ypos = pt.y;
393 }
394
395 // ---------------------------------------------------------------------------
396 // MyChild
397 // ---------------------------------------------------------------------------
398
399 unsigned MyChild::ms_numChildren = 0;
400
401 MyChild::MyChild(wxMDIParentFrame *parent)
402 : wxMDIChildFrame
403 (
404 parent,
405 wxID_ANY,
406 wxString::Format("Child %u", ++ms_numChildren)
407 )
408 {
409 m_canvas = new MyCanvas(this, wxPoint(0, 0), GetClientSize());
410
411 SetIcon(wxICON(chart));
412
413 const bool canBeResized = !IsAlwaysMaximized();
414
415 // create our menubar: it will be shown instead of the main frame one when
416 // we're active
417 #if wxUSE_MENUS
418 // Make a menubar
419 wxMenu *file_menu = new wxMenu;
420
421 file_menu->Append(wxID_NEW, "&New window\tCtrl-N");
422 file_menu->Append(wxID_CLOSE, "&Close child\tCtrl-W", "Close this window");
423 file_menu->AppendCheckItem(MDI_FULLSCREEN, "Show &fullscreen\tCtrl-F");
424 file_menu->Append(wxID_EXIT, "&Exit\tAlt-X", "Quit the program");
425
426 wxMenu *option_menu = new wxMenu;
427
428 option_menu->Append(MDI_REFRESH, "&Refresh picture");
429 option_menu->Append(MDI_CHANGE_TITLE, "Change &title...\tCtrl-T");
430 if ( canBeResized )
431 {
432 option_menu->AppendSeparator();
433 option_menu->Append(MDI_CHANGE_POSITION, "Move frame\tCtrl-M");
434 option_menu->Append(MDI_CHANGE_SIZE, "Resize frame\tCtrl-S");
435 }
436 #if wxUSE_CLIPBOARD
437 option_menu->AppendSeparator();
438 option_menu->Append(wxID_PASTE, "Copy text from clipboard\tCtrl-V");
439 #endif // wxUSE_CLIPBOARD
440
441 wxMenu *help_menu = new wxMenu;
442 help_menu->Append(wxID_ABOUT, "&About");
443
444 wxMenuBar *menu_bar = new wxMenuBar;
445
446 menu_bar->Append(file_menu, "&File");
447 menu_bar->Append(option_menu, "&Child");
448 menu_bar->Append(help_menu, "&Help");
449
450 // Associate the menu bar with the frame
451 SetMenuBar(menu_bar);
452 #endif // wxUSE_MENUS
453
454 // this should work for MDI frames as well as for normal ones, provided
455 // they can be resized at all
456 if ( canBeResized )
457 SetSizeHints(100, 100);
458 }
459
460 MyChild::~MyChild()
461 {
462 ms_numChildren--;
463 }
464
465 void MyChild::OnClose(wxCommandEvent& WXUNUSED(event))
466 {
467 Close(true);
468 }
469
470 void MyChild::OnRefresh(wxCommandEvent& WXUNUSED(event))
471 {
472 if ( m_canvas )
473 m_canvas->Refresh();
474 }
475
476 void MyChild::OnChangePosition(wxCommandEvent& WXUNUSED(event))
477 {
478 Move(10, 10);
479 }
480
481 void MyChild::OnChangeSize(wxCommandEvent& WXUNUSED(event))
482 {
483 SetClientSize(100, 100);
484 }
485
486 void MyChild::OnChangeTitle(wxCommandEvent& WXUNUSED(event))
487 {
488 #if wxUSE_TEXTDLG
489 static wxString s_title = "Canvas Frame";
490
491 wxString title = wxGetTextFromUser("Enter the new title for MDI child",
492 "MDI sample question",
493 s_title,
494 GetParent()->GetParent());
495 if ( !title )
496 return;
497
498 s_title = title;
499 SetTitle(s_title);
500 #endif // wxUSE_TEXTDLG
501 }
502
503 void MyChild::OnActivate(wxActivateEvent& event)
504 {
505 if ( event.GetActive() && m_canvas )
506 m_canvas->SetFocus();
507 }
508
509 void MyChild::OnMove(wxMoveEvent& event)
510 {
511 // VZ: here everything is totally wrong under MSW, the positions are
512 // different and both wrong (pos2 is off by 2 pixels for me which seems
513 // to be the width of the MDI canvas border)
514 wxPoint pos1 = event.GetPosition(),
515 pos2 = GetPosition();
516 wxLogStatus("position from event: (%d, %d), from frame (%d, %d)",
517 pos1.x, pos1.y, pos2.x, pos2.y);
518
519 event.Skip();
520 }
521
522 void MyChild::OnSize(wxSizeEvent& event)
523 {
524 // VZ: under MSW the size event carries the client size (quite
525 // unexpectedly) *except* for the very first one which has the full
526 // size... what should it really be? TODO: check under wxGTK
527 wxSize size1 = event.GetSize(),
528 size2 = GetSize(),
529 size3 = GetClientSize();
530 wxLogStatus("size from event: %dx%d, from frame %dx%d, client %dx%d",
531 size1.x, size1.y, size2.x, size2.y, size3.x, size3.y);
532
533 event.Skip();
534 }
535
536 void MyChild::OnCloseWindow(wxCloseEvent& event)
537 {
538 if ( m_canvas && m_canvas->IsDirty() )
539 {
540 if ( wxMessageBox("Really close?", "Please confirm",
541 wxICON_QUESTION | wxYES_NO) != wxYES )
542 {
543 event.Veto();
544
545 return;
546 }
547 }
548
549 event.Skip();
550 }
551
552 #if wxUSE_CLIPBOARD
553
554 #include "wx/clipbrd.h"
555
556 void MyChild::OnPaste(wxCommandEvent& WXUNUSED(event))
557 {
558 wxClipboardLocker lock;
559 wxTextDataObject data;
560 m_canvas->SetText(wxTheClipboard->GetData(data)
561 ? data.GetText()
562 : wxString("No text on clipboard"));
563 }
564
565 void MyChild::OnUpdatePaste(wxUpdateUIEvent& event)
566 {
567 wxClipboardLocker lock;
568 event.Enable( wxTheClipboard->IsSupported(wxDF_TEXT) );
569 }
570
571 #endif // wxUSE_CLIPBOARD