//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1991, 1996 by Borland International, All Rights Reserved
//
/// \file
/// Implementation of class TMDIClient. This defines the basic behavior
/// of all MDI client windows.
//----------------------------------------------------------------------------
#include <owl/pch.h>
#include <owl/mdi.h>
#include <algorithm>
#include <vector>
namespace owl {
OWL_DIAGINFO;
DEFINE_RESPONSE_TABLE1(TMDIClient, TWindow)
EV_COMMAND(CM_CREATECHILD, CmCreateChild),
EV_COMMAND(CM_TILECHILDREN, CmTileChildren),
EV_COMMAND(CM_TILECHILDRENHORIZ, CmTileChildrenHoriz),
EV_COMMAND(CM_CASCADECHILDREN, CmCascadeChildren),
EV_COMMAND(CM_ARRANGEICONS, CmArrangeIcons),
EV_COMMAND(CM_CLOSECHILDREN, CmCloseChildren),
EV_COMMAND_ENABLE(CM_TILECHILDREN, CmChildActionEnable),
EV_COMMAND_ENABLE(CM_TILECHILDRENHORIZ, CmChildActionEnable),
EV_COMMAND_ENABLE(CM_CASCADECHILDREN, CmChildActionEnable),
EV_COMMAND_ENABLE(CM_ARRANGEICONS, CmChildActionEnable),
EV_COMMAND_ENABLE(CM_CLOSECHILDREN, CmChildActionEnable),
EV_WM_MDICREATE,
EV_WM_MDIDESTROY,
EV_WM_DROPFILES,
END_RESPONSE_TABLE;
//
/// Constructor for a TMDIClient
//
/// Creates an MDI client window object by invoking the base class TWindow
/// constructor, passing it a null parent window, a null title, and the specified
/// library ID. Sets the default client window identifier (IDW_MDICLIENT) and sets
/// the style to include MDIS_ALLCHILDSTYLES, WS_GROUP, WS_TABSTOP, WS_CLIPCHILDREN,
/// WS_VSCROLL, and WS_HSCROLL. Initializes the ClientAttr data member, setting its
/// idFirstChild member to IDW_FIRSTMDICHILD.
//
/// Allocates a CLIENTCREATESTRUCT on the heap and
/// ClientAttr to point to this space
//
TMDIClient::TMDIClient(TModule* module)
{
// Initialize virtual base, in case the derived-most used default ctor
//
TWindow::Init(0, 0, module);
Attr.Id = IDW_MDICLIENT;
// Allow client area to grow scroll bars if necessary
//
Attr.Style |= MDIS_ALLCHILDSTYLES | WS_GROUP | WS_TABSTOP | WS_CLIPCHILDREN|
WS_CLIPSIBLINGS | WS_VSCROLL | WS_HSCROLL | WS_BORDER;
Attr.Style &= ~WS_BORDER;
Attr.ExStyle |= WS_EX_CLIENTEDGE;
ClientAttr = new CLIENTCREATESTRUCT;
ClientAttr->hWindowMenu = 0;
ClientAttr->idFirstChild = IDW_FIRSTMDICHILD;
Attr.Param = reinterpret_cast<LPVOID>(ClientAttr);
SetFlag(wfStreamTop);
}
//
/// Constructor for a TMDIClient which is being used in a DLL as an alias
/// for a non-OWL window. This ctor is generally not used by derived
/// classes
//
TMDIClient::TMDIClient(THandle handle, TModule* module)
:
TWindow(handle, module)
{
ClientAttr = 0;
SetFlag(wfStreamTop);
}
//
/// Frees the memory associated with ClientAttr
//
TMDIClient::~TMDIClient()
{
delete ClientAttr;
}
//
/// Returns TMDIClient's registration class name, "MDICLIENT."
//
auto TMDIClient::GetWindowClassName() -> TWindowClassName
{
return TWindowClassName{_T("MDICLIENT")};
}
//
/// Use the Windows message to get the active mdi child, and then down cast
/// to our MDI child derived class before returning it.
//
TMDIChild*
TMDIClient::GetActiveMDIChild()
{
THandle hWnd = THandle(HandleMessage(WM_MDIGETACTIVE));
return TYPESAFE_DOWNCAST(GetWindowPtr(hWnd), TMDIChild);
}
//
/// Arranges the MDI child window icons at the bottom of the MDI client window.
//
void
TMDIClient::ArrangeIcons()
{
HandleMessage(WM_MDIICONARRANGE);
}
//
/// Sizes and arranges all of the non-iconized MDI child windows within the MDI
/// client window. The children are overlapped, although each title bar is visible.
//
void
TMDIClient::CascadeChildren()
{
HandleMessage(WM_MDICASCADE);
}
//
/// Sizes and arranges all of the non-iconized MDI child windows within the MDI
/// client window. The children fill up the entire client area without overlapping.
//
void
TMDIClient::TileChildren(int tile)
{
HandleMessage(WM_MDITILE, tile);
}
//
/// Preprocess messages in order to translate MDI accelerator keys
///
/// If the specified msg is one of WM_KEYDOWN or WM_SYSKEYDOWN, then the keyboard
/// accelerators are translated for the MDI client.
//
bool
TMDIClient::PreProcessMsg(MSG& msg)
{
if (msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN)
return TranslateMDISysAccel(GetHandle(), &msg);
else
return false;
}
//
/// Creates the interface element associated with the MDI client window. Calls
/// TWindow::Create after first setting the child window menu in ClientAttr to the
/// parent frame window's child menu.
//
bool
TMDIClient::Create()
{
TMDIFrame* frame = TYPESAFE_DOWNCAST(GetParentO(),TMDIFrame);
CHECK(frame);
ClientAttr->hWindowMenu = frame->FindChildMenu(frame->GetMenu());
return TWindow::Create();
}
//
/// Initializes and creates a new TMDIChild by calling InitChild and Create.
//
TMDIChild*
TMDIClient::CreateChild()
{
TMDIChild* child = InitChild();
CHECK(child);
if (child->Create())
return child;
return 0;
}
//
/// Constructs an instance of a TMDIChild with this TMDIClient as parent.
/// Override this virtual function in your derived MDI client class to construct an instance
/// of a derived MDI child class. For example,
/// \code
/// TMyMDIChild* TMyMDIClient::InitChild() // override
/// {
/// return new TMyMDIChild(this);
/// }
/// \endcode
//
TMDIChild*
TMDIClient::InitChild()
{
return new TMDIChild(*this);
}
//
/// Closes the TMDIChild windows owned by this MDI client.
/// First calls TWindow::CanClose on each of the children, then if all return true, closes them.
///
/// \returns `true` is returned, if the children are closed, or if this client has no children.
/// Otherwise, `false` is returned.
///
/// \note The implementation of this function assumes that the children were dynamically allocated,
/// and have delegated ownership to the MDI parent. If a child is not dynamically allocated, or it
/// has shared ownership, it must be removed from the children list, before this function is called.
//
bool
TMDIClient::CloseChildren()
{
auto c = GetChildren();
const auto canClose = std::all_of(c.begin(), c.end(), [](TWindow& w) { return w.CanClose(); });
if (canClose)
{
// Note that the old implementation deleted the child while traversing the linked list of
// children. However, while the implementation of the iteration may be written to allow this, it
// is poor programming style to assume that iterators remain valid while mutating the underlying
// structure. So, to make the code less brittle, we now collect the children into a vector before
// deleting them.
//
// Also note that the code here assumes that the child was dynamically allocated. If not, the
// child must be taken out of the children list before this function is called.
//
auto cv = std::vector<TWindow*>{};
for (auto& w : c)
cv.push_back(&w);
for (auto& w : cv)
{
w->Destroy();
delete w;
}
}
return canClose;
}
//
/// Intercepts the WM_MDICREATE message sent when MDI child windows are created,
/// and, if the client's style includes MDIS_ALLCHILDSTYLES, and the child window's
/// specified style is 0, then changes the child window style attributes to
/// WS_VISIBLE, WS_CHILD, WS_CLIPSIBLINGS, WS_CLIPCHILDREN, WS_SYSMENU, WS_CAPTION,
/// WS_THICKFRAME, WS_MINIMIZEBOX, and WS_MAXIMIZEBOX.
//
HWND
TMDIClient::EvMDICreate(MDICREATESTRUCT & createStruct)
{
// Fill in default child window styles if they request style 0 since this
// client by default has set allchildstyles
//
if ((Attr.Style&MDIS_ALLCHILDSTYLES) && !createStruct.style)
createStruct.style =
WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
WS_SYSMENU | WS_CAPTION | WS_THICKFRAME |
WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
// Work around a Windows MDI bug w/ bad menus if MDI child is created
// maximized, by hiding child now & maximizing later
//
uint32 origStyle = createStruct.style;
if (createStruct.style & WS_MAXIMIZE)
createStruct.style &= ~(WS_MAXIMIZE | WS_VISIBLE);
TResult result = DefaultProcessing();
// Finish up maximized MDI child workaround
//
if (THandle(result) && (origStyle & WS_MAXIMIZE)) {
HandleMessage(WM_MDIMAXIMIZE, TParam1(result));
HandleMessage(WM_MDIREFRESHMENU);
}
return reinterpret_cast<HWND>(result);
}
//
/// Destroys an MDI child window.
//
void
TMDIClient::EvMDIDestroy(HWND w)
{
// When an MDI child is destroyed while other children are hidden or disabled,
// the Windows MDI child management gets confused causing subsequent failure.
// To prevent this, we temporarily unhide and enable siblings during destroy.
//
const auto unhide = [&](TWindow& child)
{
if (child.GetHandle() == w || !child.IsWindow())
return;
if (!child.IsWindowVisible())
{
child.SetWindowPos(0, TRect(), SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
child.SetFlag(wfUnHidden);
}
if (!child.IsWindowEnabled())
{
child.EnableWindow(true);
child.SetFlag(wfUnDisabled);
}
};
const auto rehide = [&](TWindow& child)
{
if (child.GetHandle() == w || !child.IsWindow())
return;
if (child.IsFlagSet(wfUnHidden))
{
child.ClearFlag(wfUnHidden);
child.ShowWindow(SW_HIDE);
}
if (child.IsFlagSet(wfUnDisabled))
{
child.ClearFlag(wfUnDisabled);
child.EnableWindow(false);
}
};
for (auto& w : GetChildren()) unhide(w);
DefaultProcessing();
for (auto& w : GetChildren()) rehide(w);
HandleMessage(WM_MDIREFRESHMENU);
}
//
/// Forwards dropped file messages by default to the parent (MDI Frame) where they
/// can be handled, or be allowed to forward to the application where they can be
/// handled more easily.
//
void
TMDIClient::EvDropFiles(TDropInfo)
{
Parent->ForwardMessage();
}
//
/// If there are MDI child windows, CmChildActionEnalbe enables any one of the child
/// window action menu items.
//
void
TMDIClient::CmChildActionEnable(TCommandEnabler& commandEnabler)
{
commandEnabler.Enable(GetFirstChild() != 0);
}
IMPLEMENT_STREAMABLE1(TMDIClient, TWindow);
#if OWL_PERSISTENT_STREAMS
//
/// Reads an instance of TMDIClient from the passed ipstream
//
void*
TMDIClient::Streamer::Read(ipstream& is, uint32 /*version*/) const
{
ReadBaseObject((TWindow*)GetObject(), is);
if (GetObject()->ClientAttr == 0)
GetObject()->ClientAttr = new CLIENTCREATESTRUCT; //
uint idFirstChild; // Need temp for near data model since ClientAttr is
is >> idFirstChild;
GetObject()->ClientAttr->idFirstChild = idFirstChild;
GetObject()->ClientAttr->hWindowMenu = (HMENU) 0;
GetObject()->Attr.Param = reinterpret_cast<LPVOID>(GetObject()->ClientAttr);
return GetObject();
}
//
/// Writes the TMDIClient to the passed opstream
//
void
TMDIClient::Streamer::Write(opstream& os) const
{
WriteBaseObject((TWindow*)GetObject(), os);
os << GetObject()->ClientAttr->idFirstChild;
}
#endif
} // OWL namespace
/* ========================================================================== */
↑ V1004 The 'child' pointer was used unsafely after it was verified against nullptr. Check lines: 182, 183.
↑ V522 There might be dereferencing of a potential null pointer 'frame'.