prevent the parent window from losing activation when a popup is shown
[wxWidgets.git] / src / msw / toplevel.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: msw/toplevel.cpp
3 // Purpose: implements wxTopLevelWindow for MSW
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 24.09.01
7 // RCS-ID: $Id$
8 // Copyright: (c) 2001 SciTech Software, Inc. (www.scitechsoft.com)
9 // License: wxWindows license
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "toplevel.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #ifndef WX_PRECOMP
32 #include "wx/app.h"
33 #include "wx/toplevel.h"
34 #include "wx/string.h"
35 #include "wx/log.h"
36 #include "wx/intl.h"
37 #include "wx/frame.h"
38 #endif //WX_PRECOMP
39
40 #include "wx/msw/private.h"
41
42 #include "wx/popupwin.h"
43
44 #ifndef ICON_BIG
45 #define ICON_BIG 1
46 #endif
47
48 #ifndef ICON_SMALL
49 #define ICON_SMALL 0
50 #endif
51
52 // ----------------------------------------------------------------------------
53 // stubs for missing functions under MicroWindows
54 // ----------------------------------------------------------------------------
55
56 #ifdef __WXMICROWIN__
57
58 // static inline bool IsIconic(HWND WXUNUSED(hwnd)) { return FALSE; }
59 static inline bool IsZoomed(HWND WXUNUSED(hwnd)) { return FALSE; }
60
61 #endif // __WXMICROWIN__
62
63 // ----------------------------------------------------------------------------
64 // globals
65 // ----------------------------------------------------------------------------
66
67 // list of all frames and modeless dialogs
68 wxWindowList wxModelessWindows;
69
70 // the name of the default wxWindows class
71 extern const wxChar *wxCanvasClassName;
72
73 // ============================================================================
74 // wxTopLevelWindowMSW implementation
75 // ============================================================================
76
77 // ----------------------------------------------------------------------------
78 // wxDialog helpers
79 // ----------------------------------------------------------------------------
80
81 // Dialog window proc
82 LONG APIENTRY _EXPORT
83 wxDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
84 {
85 switch ( message )
86 {
87 case WM_INITDIALOG:
88 // for this message, returning TRUE tells system to set focus to the
89 // first control in the dialog box
90 return TRUE;
91
92 default:
93 // for all the other ones, FALSE means that we didn't process the
94 // message
95 return FALSE;
96 }
97 }
98
99 // ----------------------------------------------------------------------------
100 // wxTopLevelWindowMSW creation
101 // ----------------------------------------------------------------------------
102
103 void wxTopLevelWindowMSW::Init()
104 {
105 m_iconized =
106 m_maximizeOnShow = FALSE;
107
108 // unlike (almost?) all other windows, frames are created hidden
109 m_isShown = FALSE;
110
111 // Data to save/restore when calling ShowFullScreen
112 m_fsStyle = 0;
113 m_fsOldWindowStyle = 0;
114 m_fsIsMaximized = FALSE;
115 m_fsIsShowing = FALSE;
116 }
117
118 WXDWORD wxTopLevelWindowMSW::MSWGetStyle(long style, WXDWORD *exflags) const
119 {
120 // let the base class deal with the common styles but fix the ones which
121 // don't make sense for us (we also deal with the borders ourselves)
122 WXDWORD msflags = wxWindow::MSWGetStyle
123 (
124 (style & ~wxBORDER_MASK) | wxBORDER_NONE, exflags
125 ) & ~WS_CHILD;
126
127 // first select the kind of window being created
128 //
129 // note that if we don't set WS_POPUP, Windows assumes WS_OVERLAPPED and
130 // creates a window with both caption and border, hence we also test it
131 // below in some other cases
132 if ( style & wxFRAME_TOOL_WINDOW )
133 msflags |= WS_POPUP;
134 else
135 msflags |= WS_OVERLAPPED;
136
137 // border and caption styles
138 if ( style & wxRESIZE_BORDER )
139 msflags |= WS_THICKFRAME;
140 else if ( !(style & wxBORDER_NONE) )
141 msflags |= WS_BORDER;
142 else
143 msflags |= WS_POPUP;
144
145 if ( style & wxCAPTION )
146 msflags |= WS_CAPTION;
147 else
148 msflags |= WS_POPUP;
149
150 // next translate the individual flags
151 if ( style & wxMINIMIZE_BOX )
152 msflags |= WS_MINIMIZEBOX;
153 if ( style & wxMAXIMIZE_BOX )
154 msflags |= WS_MAXIMIZEBOX;
155 if ( style & wxSYSTEM_MENU )
156 msflags |= WS_SYSMENU;
157 if ( style & wxMINIMIZE )
158 msflags |= WS_MINIMIZE;
159 if ( style & wxMAXIMIZE )
160 msflags |= WS_MAXIMIZE;
161
162 // Keep this here because it saves recoding this function in wxTinyFrame
163 #if wxUSE_ITSY_BITSY && !defined(__WIN32__)
164 if ( style & wxTINY_CAPTION_VERT )
165 msflags |= IBS_VERTCAPTION;
166 if ( style & wxTINY_CAPTION_HORIZ )
167 msflags |= IBS_HORZCAPTION;
168 #else
169 if ( style & (wxTINY_CAPTION_VERT | wxTINY_CAPTION_HORIZ) )
170 msflags |= WS_CAPTION;
171 #endif
172
173 if ( exflags )
174 {
175 #if !defined(__WIN16__) && !defined(__SC__)
176 if ( !(GetExtraStyle() & wxTOPLEVEL_EX_DIALOG) )
177 {
178 // make all frames appear in the win9x shell taskbar unless
179 // wxFRAME_TOOL_WINDOW or wxFRAME_NO_TASKBAR is given - without
180 // giving them WS_EX_APPWINDOW style, the child (i.e. owned) frames
181 // wouldn't appear in it
182 if ( (style & wxFRAME_TOOL_WINDOW) || (style & wxFRAME_NO_TASKBAR) )
183 *exflags |= WS_EX_TOOLWINDOW;
184 else if ( !(style & wxFRAME_NO_TASKBAR) )
185 *exflags |= WS_EX_APPWINDOW;
186 }
187 #endif // !Win16
188
189 if ( style & wxSTAY_ON_TOP )
190 *exflags |= WS_EX_TOPMOST;
191
192 #ifdef __WIN32__
193 if ( GetExtraStyle() & wxFRAME_EX_CONTEXTHELP )
194 *exflags |= WS_EX_CONTEXTHELP;
195 #endif // __WIN32__
196 }
197
198 return msflags;
199 }
200
201 bool wxTopLevelWindowMSW::CreateDialog(const void *dlgTemplate,
202 const wxString& title,
203 const wxPoint& pos,
204 const wxSize& size)
205 {
206 #ifdef __WXMICROWIN__
207 // no dialogs support under MicroWin yet
208 return CreateFrame(title, pos, size);
209 #else // !__WXMICROWIN__
210 wxWindow *parent = GetParent();
211
212 // for the dialogs without wxDIALOG_NO_PARENT style, use the top level
213 // app window as parent - this avoids creating modal dialogs without
214 // parent
215 if ( !parent && !(GetWindowStyleFlag() & wxDIALOG_NO_PARENT) )
216 {
217 parent = wxTheApp->GetTopWindow();
218
219 if ( parent )
220 {
221 // don't use transient windows as parents, this is dangerous as it
222 // can lead to a crash if the parent is destroyed before the child
223 //
224 // also don't use the window which is currently hidden as then the
225 // dialog would be hidden as well
226 if ( (parent->GetExtraStyle() & wxWS_EX_TRANSIENT) ||
227 !parent->IsShown() )
228 {
229 parent = NULL;
230 }
231 }
232 }
233
234 m_hWnd = (WXHWND)::CreateDialogIndirect
235 (
236 wxGetInstance(),
237 (DLGTEMPLATE*)dlgTemplate,
238 parent ? GetHwndOf(parent) : NULL,
239 (DLGPROC)wxDlgProc
240 );
241
242 if ( !m_hWnd )
243 {
244 wxFAIL_MSG(_("Failed to create dialog. Incorrect DLGTEMPLATE?"));
245
246 wxLogSysError(_("Can't create dialog using memory template"));
247
248 return FALSE;
249 }
250
251 WXDWORD exflags;
252 (void)MSWGetCreateWindowFlags(&exflags);
253
254 if ( exflags )
255 {
256 ::SetWindowLong(GetHwnd(), GWL_EXSTYLE, exflags);
257 ::SetWindowPos(GetHwnd(), NULL, 0, 0, 0, 0,
258 SWP_NOSIZE |
259 SWP_NOMOVE |
260 SWP_NOZORDER |
261 SWP_NOACTIVATE);
262 }
263
264 #if defined(__WIN95__)
265 // For some reason, the system menu is activated when we use the
266 // WS_EX_CONTEXTHELP style, so let's set a reasonable icon
267 if ( exflags & WS_EX_CONTEXTHELP )
268 {
269 wxFrame *winTop = wxDynamicCast(wxTheApp->GetTopWindow(), wxFrame);
270 if ( winTop )
271 {
272 wxIcon icon = winTop->GetIcon();
273 if ( icon.Ok() )
274 {
275 ::SendMessage(GetHwnd(), WM_SETICON,
276 (WPARAM)TRUE,
277 (LPARAM)GetHiconOf(icon));
278 }
279 }
280 }
281 #endif // __WIN95__
282
283 // move the dialog to its initial position without forcing repainting
284 int x, y, w, h;
285 if ( !MSWGetCreateWindowCoords(pos, size, x, y, w, h) )
286 {
287 x =
288 w = (int)CW_USEDEFAULT;
289 }
290
291 // we can't use CW_USEDEFAULT here as we're not calling CreateWindow()
292 // and passing CW_USEDEFAULT to MoveWindow() results in resizing the
293 // window to (0, 0) size which breaks quite a lot of things, e.g. the
294 // sizer calculation in wxSizer::Fit()
295 if ( w == (int)CW_USEDEFAULT )
296 {
297 // the exact number doesn't matter, the dialog will be resized
298 // again soon anyhow but it should be big enough to allow
299 // calculation relying on "totalSize - clientSize > 0" work, i.e.
300 // at least greater than the title bar height
301 w =
302 h = 100;
303 }
304
305 if ( x == (int)CW_USEDEFAULT )
306 {
307 // centre it on the screen - what else can we do?
308 wxSize sizeDpy = wxGetDisplaySize();
309
310 x = (sizeDpy.x - w) / 2;
311 y = (sizeDpy.y - h) / 2;
312 }
313
314 if ( !::MoveWindow(GetHwnd(), x, y, w, h, FALSE) )
315 {
316 wxLogLastError(wxT("MoveWindow"));
317 }
318
319 if ( !title.empty() )
320 {
321 ::SetWindowText(GetHwnd(), title);
322 }
323
324 SubclassWin(m_hWnd);
325
326 return TRUE;
327 #endif // __WXMICROWIN__/!__WXMICROWIN__
328 }
329
330 bool wxTopLevelWindowMSW::CreateFrame(const wxString& title,
331 const wxPoint& pos,
332 const wxSize& size)
333 {
334 WXDWORD exflags;
335 WXDWORD flags = MSWGetCreateWindowFlags(&exflags);
336
337 return MSWCreate(wxCanvasClassName, title, pos, size, flags, exflags);
338 }
339
340 bool wxTopLevelWindowMSW::Create(wxWindow *parent,
341 wxWindowID id,
342 const wxString& title,
343 const wxPoint& pos,
344 const wxSize& size,
345 long style,
346 const wxString& name)
347 {
348 // init our fields
349 Init();
350
351 m_windowStyle = style;
352
353 SetName(name);
354
355 m_windowId = id == -1 ? NewControlId() : id;
356
357 wxTopLevelWindows.Append(this);
358
359 if ( parent )
360 parent->AddChild(this);
361
362 if ( GetExtraStyle() & wxTOPLEVEL_EX_DIALOG )
363 {
364 // we have different dialog templates to allows creation of dialogs
365 // with & without captions under MSWindows, resizeable or not (but a
366 // resizeable dialog always has caption - otherwise it would look too
367 // strange)
368
369 // we need 3 additional WORDs for dialog menu, class and title (as we
370 // don't use DS_SETFONT we don't need the fourth WORD for the font)
371 static const int dlgsize = sizeof(DLGTEMPLATE) + (sizeof(WORD) * 3);
372 DLGTEMPLATE *dlgTemplate = (DLGTEMPLATE *)malloc(dlgsize);
373 memset(dlgTemplate, 0, dlgsize);
374
375 // these values are arbitrary, they won't be used normally anyhow
376 dlgTemplate->x = 34;
377 dlgTemplate->y = 22;
378 dlgTemplate->cx = 144;
379 dlgTemplate->cy = 75;
380
381 // reuse the code in MSWGetStyle() but correct the results slightly for
382 // the dialog
383 dlgTemplate->style = MSWGetStyle(style, NULL);
384
385 // all dialogs are popups
386 dlgTemplate->style |= WS_POPUP;
387
388 // force 3D-look if necessary, it looks impossibly ugly otherwise
389 if ( style & (wxRESIZE_BORDER | wxCAPTION) )
390 dlgTemplate->style |= DS_MODALFRAME;
391
392 bool ret = CreateDialog(dlgTemplate, title, pos, size);
393 free(dlgTemplate);
394
395 return ret;
396 }
397 else // !dialog
398 {
399 return CreateFrame(title, pos, size);
400 }
401 }
402
403 wxTopLevelWindowMSW::~wxTopLevelWindowMSW()
404 {
405 wxTopLevelWindows.DeleteObject(this);
406
407 if ( wxModelessWindows.Find(this) )
408 wxModelessWindows.DeleteObject(this);
409
410 // If this is the last top-level window, exit.
411 if ( wxTheApp && (wxTopLevelWindows.Number() == 0) )
412 {
413 wxTheApp->SetTopWindow(NULL);
414
415 if ( wxTheApp->GetExitOnFrameDelete() )
416 {
417 ::PostQuitMessage(0);
418 }
419 }
420 }
421
422 // ----------------------------------------------------------------------------
423 // wxTopLevelWindowMSW showing
424 // ----------------------------------------------------------------------------
425
426 void wxTopLevelWindowMSW::DoShowWindow(int nShowCmd)
427 {
428 ::ShowWindow(GetHwnd(), nShowCmd);
429
430 m_iconized = nShowCmd == SW_MINIMIZE;
431 }
432
433 bool wxTopLevelWindowMSW::Show(bool show)
434 {
435 // don't use wxWindow version as we want to call DoShowWindow() ourselves
436 if ( !wxWindowBase::Show(show) )
437 return FALSE;
438
439 int nShowCmd;
440 if ( show )
441 {
442 if ( m_maximizeOnShow )
443 {
444 // show and maximize
445 nShowCmd = SW_MAXIMIZE;
446
447 m_maximizeOnShow = FALSE;
448 }
449 else // just show
450 {
451 nShowCmd = SW_SHOW;
452 }
453 }
454 else // hide
455 {
456 nShowCmd = SW_HIDE;
457 }
458
459 DoShowWindow(nShowCmd);
460
461 if ( show )
462 {
463 ::BringWindowToTop(GetHwnd());
464
465 wxActivateEvent event(wxEVT_ACTIVATE, TRUE, m_windowId);
466 event.SetEventObject( this );
467 GetEventHandler()->ProcessEvent(event);
468 }
469 else // hide
470 {
471 // Try to highlight the correct window (the parent)
472 if ( GetParent() )
473 {
474 HWND hWndParent = GetHwndOf(GetParent());
475 if (hWndParent)
476 ::BringWindowToTop(hWndParent);
477 }
478 }
479
480 return TRUE;
481 }
482
483 // ----------------------------------------------------------------------------
484 // wxTopLevelWindowMSW maximize/minimize
485 // ----------------------------------------------------------------------------
486
487 void wxTopLevelWindowMSW::Maximize(bool maximize)
488 {
489 if ( IsShown() )
490 {
491 // just maximize it directly
492 DoShowWindow(maximize ? SW_MAXIMIZE : SW_RESTORE);
493 }
494 else // hidden
495 {
496 // we can't maximize the hidden frame because it shows it as well, so
497 // just remember that we should do it later in this case
498 m_maximizeOnShow = TRUE;
499 }
500 }
501
502 bool wxTopLevelWindowMSW::IsMaximized() const
503 {
504 return ::IsZoomed(GetHwnd()) != 0;
505 }
506
507 void wxTopLevelWindowMSW::Iconize(bool iconize)
508 {
509 DoShowWindow(iconize ? SW_MINIMIZE : SW_RESTORE);
510 }
511
512 bool wxTopLevelWindowMSW::IsIconized() const
513 {
514 // also update the current state
515 ((wxTopLevelWindowMSW *)this)->m_iconized = ::IsIconic(GetHwnd()) != 0;
516
517 return m_iconized;
518 }
519
520 void wxTopLevelWindowMSW::Restore()
521 {
522 DoShowWindow(SW_RESTORE);
523 }
524
525 // ----------------------------------------------------------------------------
526 // wxTopLevelWindowMSW fullscreen
527 // ----------------------------------------------------------------------------
528
529 bool wxTopLevelWindowMSW::ShowFullScreen(bool show, long style)
530 {
531 if (show)
532 {
533 if (IsFullScreen())
534 return FALSE;
535
536 m_fsIsShowing = TRUE;
537 m_fsStyle = style;
538
539 // zap the frame borders
540
541 // save the 'normal' window style
542 m_fsOldWindowStyle = GetWindowLong((HWND)GetHWND(), GWL_STYLE);
543
544 // save the old position, width & height, maximize state
545 m_fsOldSize = GetRect();
546 m_fsIsMaximized = IsMaximized();
547
548 // decide which window style flags to turn off
549 LONG newStyle = m_fsOldWindowStyle;
550 LONG offFlags = 0;
551
552 if (style & wxFULLSCREEN_NOBORDER)
553 offFlags |= WS_BORDER | WS_THICKFRAME;
554 if (style & wxFULLSCREEN_NOCAPTION)
555 offFlags |= (WS_CAPTION | WS_SYSMENU);
556
557 newStyle &= (~offFlags);
558
559 // change our window style to be compatible with full-screen mode
560 ::SetWindowLong((HWND)GetHWND(), GWL_STYLE, newStyle);
561
562 // resize to the size of the desktop
563 int width, height;
564
565 RECT rect = wxGetWindowRect(::GetDesktopWindow());
566 width = rect.right - rect.left;
567 height = rect.bottom - rect.top;
568
569 SetSize(width, height);
570
571 // now flush the window style cache and actually go full-screen
572 SetWindowPos((HWND)GetHWND(), HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED);
573
574 wxSizeEvent event(wxSize(width, height), GetId());
575 GetEventHandler()->ProcessEvent(event);
576
577 return TRUE;
578 }
579 else
580 {
581 if (!IsFullScreen())
582 return FALSE;
583
584 m_fsIsShowing = FALSE;
585
586 Maximize(m_fsIsMaximized);
587 SetWindowLong((HWND)GetHWND(),GWL_STYLE, m_fsOldWindowStyle);
588 SetWindowPos((HWND)GetHWND(),HWND_TOP,m_fsOldSize.x, m_fsOldSize.y,
589 m_fsOldSize.width, m_fsOldSize.height, SWP_FRAMECHANGED);
590
591 return TRUE;
592 }
593 }
594
595 // ----------------------------------------------------------------------------
596 // wxTopLevelWindowMSW misc
597 // ----------------------------------------------------------------------------
598
599 void wxTopLevelWindowMSW::SetIcon(const wxIcon& icon)
600 {
601 SetIcons( wxIconBundle( icon ) );
602 }
603
604 void wxTopLevelWindowMSW::SetIcons(const wxIconBundle& icons)
605 {
606 wxTopLevelWindowBase::SetIcons(icons);
607
608 #if defined(__WIN95__) && !defined(__WXMICROWIN__)
609 const wxIcon& sml = icons.GetIcon( wxSize( 16, 16 ) );
610 if( sml.Ok() && sml.GetWidth() == 16 && sml.GetHeight() == 16 )
611 {
612 ::SendMessage( GetHwndOf( this ), WM_SETICON, ICON_SMALL,
613 (LPARAM)GetHiconOf(sml) );
614 }
615
616 const wxIcon& big = icons.GetIcon( wxSize( 32, 32 ) );
617 if( big.Ok() && big.GetWidth() == 32 && big.GetHeight() == 32 )
618 {
619 ::SendMessage( GetHwndOf( this ), WM_SETICON, ICON_BIG,
620 (LPARAM)GetHiconOf(big) );
621 }
622 #endif // __WIN95__
623 }
624
625 bool wxTopLevelWindowMSW::EnableCloseButton(bool enable)
626 {
627 #ifndef __WXMICROWIN__
628 // get system (a.k.a. window) menu
629 HMENU hmenu = ::GetSystemMenu(GetHwnd(), FALSE /* get it */);
630 if ( !hmenu )
631 {
632 wxLogLastError(_T("GetSystemMenu"));
633
634 return FALSE;
635 }
636
637 // enabling/disabling the close item from it also automatically
638 // disables/enables the close title bar button
639 if ( ::EnableMenuItem(hmenu, SC_CLOSE,
640 MF_BYCOMMAND |
641 (enable ? MF_ENABLED : MF_GRAYED)) == -1 )
642 {
643 wxLogLastError(_T("EnableMenuItem(SC_CLOSE)"));
644
645 return FALSE;
646 }
647
648 // update appearance immediately
649 if ( !::DrawMenuBar(GetHwnd()) )
650 {
651 wxLogLastError(_T("DrawMenuBar"));
652 }
653 #endif // !__WXMICROWIN__
654
655 return TRUE;
656 }
657
658 // ----------------------------------------------------------------------------
659 // wxTopLevelWindowMSW message processing
660 // ----------------------------------------------------------------------------
661
662 long wxTopLevelWindowMSW::HandleNcActivate(bool activate)
663 {
664 #if wxUSE_POPUPWIN
665 /*
666 Normally, when another top level (whether it is overlapped or popup)
667 window is shown, it is activated and the parent window (i.e. we) loses
668 the activation. This, however, looks very ugly when the child window is
669 a [custom] combobox which we implement using a popup window as surely
670 opening a combobox shouldn't result in deactivating the parent window.
671
672 So we don't redraw the title bar in this case, even if we still return
673 TRUE to let the change of activation to take place as otherwise the
674 controls inside the popup window wouldn't work properly.
675 */
676 if ( !activate && wxPopupWindow::FindPopupFor(this) )
677 {
678 return TRUE;
679 }
680 #endif // wxUSE_POPUPWIN
681
682 return FALSE;
683 }
684
685 long
686 wxTopLevelWindowMSW::MSWWindowProc(WXUINT msg, WXWPARAM wParam, WXLPARAM lParam)
687 {
688 if ( msg == WM_NCACTIVATE && HandleNcActivate(wParam != 0) )
689 {
690 // we processed WM_NCACTIVATE ourselves
691 return TRUE;
692 }
693
694 return wxTopLevelWindowBase::MSWWindowProc(msg, wParam, lParam);
695 }