//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1991, 1996 by Borland International, All Rights Reserved
//
/// \file
/// Implementation of TWindow. This defines the basic behavior of all Windows.
//----------------------------------------------------------------------------
#include <owl/pch.h>
#include <owl/defs.h>
#include <owl/window.h>
#include <owl/applicat.h>
#include <owl/appdict.h>
#include <owl/scroller.h>
#include <owl/gdiobjec.h>
#include <owl/menu.h>
#include <owl/framewin.h>
#include <owl/commctrl.h>
#include <owl/shellitm.h>
#include <owl/tooltip.h>
#if defined(BI_MULTI_THREAD_RTL)
#include <owl/thread.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <owl/registry.h>
#include <owl/hlpmanag.h> //THelpHitInfo
#include <array>
#include <vector>
#include <algorithm>
#if defined(__BORLANDC__)
# pragma option -w-ccc // Disable "Condition is always true/false"
#endif
using namespace std;
namespace owl {
OWL_DIAGINFO;
DIAG_DECLARE_GROUP(OwlMsg); // diagnostic group for message tracing
DIAG_DEFINE_GROUP_INIT(OWL_INI, OwlWin, 1, 0); // diag. group for windows
DIAG_DEFINE_GROUP_INIT(OWL_INI, OwlCmd, 1, 0); // diag. group for commands
namespace {
//
// Attempts to retrieve the system setting for mouse wheel scrolling granularity.
// If retrieval fails, the failure is logged (in debug mode) and the system default is returned.
//
auto GetWheelScrollLines_() -> uint
{
auto n = 3u; // System default.
const auto r = ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &n, 0);
WARN(!r, _T("SystemParametersInfo(SPI_GETWHEELSCROLLLINES) failed. LastError: ") << GetLastError()); InUse(r);
return n;
};
} // namespace
} // OWL namespace
//
// Define to use rtti to create unique window ids for the message cache
//
#define OWL_RTTI_MSGCACHE
#define TYPE_UNIQUE_ID(t) reinterpret_cast<UINT_PTR>(typeid(t).name())
bool gBatchMode = false; // true if we were invoked to do batch processing
// which means windows should be invisible and whatnot
// currently this is only set if we have an output parameter
namespace owl {
//
// Externs defined in owl.cpp
//
extern _OWLFUNC(void) SetCreationWindow(owl::TWindow*);
extern _OWLFUNC(owl::TWindow*) GetCreationWindow();
extern _OWLDATA(uint) GetWindowPtrMsgId;
DEFINE_RESPONSE_TABLE(TWindow)
EV_WM_SETCURSOR,
EV_WM_SIZE,
EV_WM_MOVE,
EV_WM_COMPAREITEM,
EV_WM_DELETEITEM,
EV_WM_DRAWITEM,
EV_WM_MEASUREITEM,
EV_WM_VSCROLL,
EV_WM_HSCROLL,
EV_WM_CHILDINVALID,
EV_WM_ERASEBKGND,
EV_WM_CTLCOLORBTN(EvCtlColor),
EV_WM_CTLCOLORDLG(EvCtlColor),
EV_WM_CTLCOLOREDIT(EvCtlColor),
EV_WM_CTLCOLORLISTBOX(EvCtlColor),
#if defined(OWL5_COMPAT)
EV_WM_CTLCOLORMSGBOX(EvCtlColor), // This entry is deprecated. See TDispatch<WM_CTLCOLORMSGBOX>.
#endif
EV_WM_CTLCOLORSCROLLBAR(EvCtlColor),
EV_WM_CTLCOLORSTATIC(EvCtlColor),
EV_WM_PAINT,
EV_WM_LBUTTONDOWN,
EV_WM_KILLFOCUS,
EV_WM_CREATE,
EV_WM_CLOSE,
EV_WM_DESTROY,
EV_WM_NCDESTROY,
EV_WM_QUERYENDSESSION,
EV_WM_ENDSESSION,
EV_WM_SYSCOLORCHANGE,
EV_WM_INITMENUPOPUP,
EV_WM_CONTEXTMENU,
EV_WM_ENTERIDLE,
EV_WM_MOUSEWHEEL,
EV_WM_MOUSEHWHEEL,
EV_WM_SETFONT,
END_RESPONSE_TABLE;
/// \class TCommandEnabler
///
/// An abstract base class used for automatic enabling and disabling of commands,
/// TCommandEnabler is a class from which you can derive other classes, each one
/// having its own command enabler. For example, TButtonGadgetEnabler is a derived
/// class that's a command enabler for button gadgets, and TMenuItemEnabler is a
/// derived class that's a command enabler for menu items. Although your derived
/// classes are likely to use only the functions Enable, SetCheck, and GetHandled,
/// all of TCommandEnabler's functions are described so that you can better
/// understand how ObjectWindows uses command processing. The following paragraphs
/// explain the dynamics of command processing.
///
///
/// <b>Handling command messages</b>
///
/// Commands are messages of the windows WM_COMMAND type that have associated
/// command identifiers (for example, CM_FILEMENU). When the user selects an item
/// from a menu or a toolbar, when a control sends a notification message to its
/// parent window, or when an accelerator keystroke is translated, a WM_COMMAND
/// message is sent to a window.
///
///
/// <b>Responding to command messages</b>
///
/// A command is handled differently depending on which type of command a window
/// receives. Menu items and accelerator commands are handled by adding a command
/// entry to a message response table using the EV_COMMAND macro. The entry requires
/// two arguments:
/// - A command identifier (for example, CM_LISTUNDO)
/// - A member function (for example, CMEditUndo)
///
/// Child ID notifications, messages that a child window sends to its parent window,
/// are handling by using one of the notification macros defined in the header file
/// windowev.h.
/// It is also possible to handle a child ID notification at the child window by
/// adding an entry to the child's message response table using the
/// EV_NOTIFY_AT_CHILD macro. This entry requires the following arguments:
/// - A notification message (for example, LBN_DBLCLK)
/// - A member function (for example, CmEditItem)
///
/// <b>TWindow command processing</b>
///
/// One of the classes designed to handle command processing, TWindow performs basic
/// command processing according to these steps:
/// - 1. The member function WindowProc calls the virtual member function EvCommand.
/// - 2. EvCommand checks to see if the window has requested handling the command by
/// looking up the command in the message response table.
/// - 3. If the window has requested handling the command identifier by using the
/// EV_COMMAND macro, the command is dispatched.
///
/// TWindow also handles Child ID notifications at the child window level.
///
///
/// <b>TFrameWindow command processing</b>
///
/// TFrameWindow provides specialized command processing by overriding its member
/// function EvCommand and sending the command down the command chain (that is, the
/// chain of windows from the focus window back up to the frame itself, the original
/// receiver of the command message).
///
/// If no window in the command chain handles the command, TFrameWindow delegates
/// the command to the application object. Although this last step is theoretically
/// performed by the frame window, it is actually done by TWindow's member function,
/// DefaultProcessing().
///
///
/// <b>Invoking EvCommand</b>
///
/// When TFrameWindow sends a command down the command chain, it does not directly
/// dispatch the command; instead, it invokes the window's EvCommand member
/// function. This procedure gives the windows in the command chain the flexibility
/// to handle a command by overriding the member function EvCommand instead of being
/// limited to handling only the commands requested by the EV_COMMAND macro.
///
///
/// <b>Handling command enable messages</b>
///
/// Most applications expend considerable energy updating menu items and tool bar
/// buttons to provide the necessary feedback indicating that a command has been
/// enabled. In order to simplify this procedure, ObjectWindows lets the event
/// handler that is going to handle the command make the decision about whether or
/// not to enable or disable a command.
///
/// Although the WM_COMMAND_ENABLE message is sent down the same command chain as
/// the WM_COMMAND event; exactly when the WM_COMMAND_ENABLE message is sent depends
/// on the type of command enabling that needs to be processed.
///
///
/// <b>Command enabling for menu items</b>
///
/// TFrameWindow performs this type of command enabling when it receives a
/// WM_INITMENUPOPUP message. It sends this message before a menu list appears.
/// ObjectWindows then identifies the menu commands using the command IDs and sends
/// requests for the commands to be enabled.
///
/// Note that because Object Windows actively maintains toolbars and menu items, any
/// changes made to the variables involved in the command enabling functions are
/// implemented dynamically and not just when a window is activated.
///
///
/// <b>Command enabling for toolbar buttons</b>
///
/// This type of command enabling is performed during idle processing (in the
/// IdleAction function). The Default Message-Processing Flowchart that accompanies
/// TWindow::DefaultProcessing is a graphical illustration of this process.
///
///
/// <b>Creating specialized command enablers</b>
///
/// Associated with the WM_COMMAND_ENABLE message is an object of the
/// TCommandEnabler type. This family of command enablers includes specialized
/// command enablers for menu items and toolbar buttons.
///
/// As you can see from TCommandEnabler's class declaration, you can do
/// considerably more than simply enable or disable a command using the command
/// enabler. For example, you have the ability to change the text associated with
/// the command as well as the state of the command.
///
///
/// <b>Using the EV_COMMAND_ENABLE macro</b>
///
/// You can use the EV_COMMAND_ENABLE macro to handle WM_COMMAND_ENABLE messages.
/// Just as you do with the EV_COMMAND macro, you specify the command identifier
/// that you want to handle and the member function you want to invoke to handle the
/// message.
///
///
/// <b>Automatically enabling and disabling commands</b>
///
/// ObjectWindows simplifies enabling and disabling of commands by automatically
/// disabling commands for which there are no associated handlers. TFrameWindow's
/// member function EvCommandEnable performs this operation, which involves
/// completing a two-pass algorithm:
/// - 1. The first pass sends a WM_COMMAND_ENABLE message down the command chain
/// giving each window an explicit opportunity to do the command enabling.
/// - 2. If no window handles the command enabling request, then ObjectWindows checks
/// to see whether any windows in the command chain are going to handle the command
/// through any associated EV_COMMAND entries in their response tables. If there is
/// a command handler in one of the response tables, then the command is enabled;
/// otherwise it is disabled.
///
/// Because of this implicit command enabling or disabling, you do not need to (and
/// actually should not) do explicit command enabling unless you want to change the
/// command text, change the command state, or conditionally enable or disable the
/// command.
///
/// If you handle commands indirectly by overriding the member function EvCommand
/// instead of using the EV_COMMAND macro to add a response table entry, then
/// ObjectWindows will not be aware that you are handling the command. Consequently,
/// the command may be automatically disabled. Should this occur, the appropriate
/// action to take is to also override the member function EvCommandEnable and
/// explicitly enable the command.
//
/// Constructs the TCommandEnabler object with the specified command ID. Sets the
/// message responder (hWndReceiver) to zero.
//
TCommandEnabler::TCommandEnabler(uint id, HWND hReceiver)
:
Id(id),
HWndReceiver(hReceiver)
{
Flags = 0;
}
//
/// Enables or disables the command sender. When Enable is called, it sets the
/// Handled flag.
//
void
TCommandEnabler::Enable(bool)
{
Flags |= WasHandled;
}
void TWindow::TraceWindowPlacement()
{
auto wp = GetWindowPlacement();
TRACEX(OwlWin, 1,
"PLACEMENT=" << TRect{wp.rcNormalPosition} <<
" handle=" << GetHandle() <<
" min=" << TPoint{wp.ptMinPosition} <<
" max=" << TPoint{wp.ptMaxPosition} <<
" showCmd=" << wp.showCmd <<
" flags=" << wp.flags);
}
//
/// Adds this to the child list of parent if nonzero, and calls EnableAutoCreate so
/// that this will be created and displayed along with parent. Also sets the title
/// of the window and initializes the window's creation attributes.
///
/// The following paragraphs describe procedures common to both constructor
/// syntaxes. module specifies the application or DLL that owns the TWindow
/// instance. ObjectWindows needs the correct value of module to find needed
/// resources. If module is 0, TWindow sets its module according to the following
/// rules:
/// - If the window has a parent, the parent's module is used.
/// - If the TWindow constructor is invoked from an application, the
/// module is set to the application.
/// - If the TWindow constructor is invoked from a DLL that is
/// dynamically linked with the ObjectWindows DLL and the currently running
/// application is linked the same way, the module is set to the currently running
/// application.
/// - If the TWindow constructor is invoked from a DLL that is
/// statically linked with the ObjectWindows library or the invoking DLL is
/// dynamically linked with ObjectWindows DLL but the currently running application
/// is not, no default is used for setting the module. Instead, a TXInvalidModule
/// exception is thrown and the object is not created.
//
TWindow::TWindow(TWindow* parent, LPCTSTR title, TModule* module)
: InstanceProc(0), DefaultProc(0), Handle(0), Title(0), Module(0)
{
Init(parent, title, module);
}
//
/// String-aware overload
//
TWindow::TWindow(TWindow* parent, const tstring& title, TModule* module)
: InstanceProc(0), DefaultProc(0), Handle(0), Title(0), Module(0)
{
Init(parent, title, module);
}
//
/// Constructs a TWindow that is used as an alias for a non-ObjectWindows window,
/// and sets wfAlias. Because the HWND is already available, this constructor,
/// unlike the other TWindow constructor, performs the "thunking" and extraction of
/// HWND information instead of waiting until the function Create creates the
/// interface element.
///
/// The following paragraphs describe procedures common to both constructor
/// syntaxes. module specifies the application or DLL that owns the TWindow
/// instance. ObjectWindows needs the correct value of module to find needed
/// resources. If module is 0, TWindow sets its module according to the following
/// rules:
/// - If the window has a parent, the parent's module is used.
/// - If the TWindow constructor is invoked from an application, the
/// module is set to the application.
/// - If the TWindow constructor is invoked from a DLL that is
/// dynamically linked with the ObjectWindows DLL and the currently running
/// application is linked the same way, the module is set to the currently running
/// application.
/// - If the TWindow constructor is invoked from a DLL that is
/// statically linked with the ObjectWindows library or the invoking DLL is
/// dynamically linked with ObjectWindows DLL but the currently running application
/// is not, no default is used for setting the module. Instead, a TXInvalidModule
/// exception is thrown and the object is not created.
TWindow::TWindow(HWND handle, TModule* module)
: InstanceProc(0), DefaultProc(0), Handle(0), Title(0), Module(0)
{
PRECONDITION(handle);
Init(handle, module);
}
//
/// Protected constructor for use by immediate virtually derived classes.
/// Immediate derivitives must call an Init() before constructions are done.
//
TWindow::TWindow()
: InstanceProc(0), DefaultProc(0), Handle(0), Title(0), Module(0)
{
}
//
/// Normal initialization of a default constructed TWindow. Is ignored if
/// called more than once.
//
void
TWindow::Init(TWindow* parent, LPCTSTR title, TModule* module)
{
if (!InstanceProc) {
SetCaption(title);
PerformInit(parent, module);
EnableAutoCreate();
// Initialize creation attributes
//
Attr.Style = WS_CHILD | WS_VISIBLE;
Attr.X = Attr.Y = Attr.W = Attr.H = 0;
Attr.ExStyle = 0;
Attr.Id = 0;
}
}
//
/// Wrapper initialization of a default constructed TWindow. Is ignored if
/// called more than once.
//
void
TWindow::Init(HWND handle, TModule* module)
{
PRECONDITION(handle);
if (!InstanceProc) {
// Perform preliminary initialization
//
SetHandle(handle);
SetCaption(LPCTSTR(0));
// If we've been given a module, then setup that and the app now if
// possible so that GetWindowPtr below has some context. Otherwise at least
// set it to 0.
//
Application = TYPESAFE_DOWNCAST(module,TApplication);
// If the receiver's parent is an OWL window then pass the window to
// PerformInit so the receiver can be added to the parent's list of children.
//
HWND hParent = GetParentH();
TWindow* parent = hParent ? GetWindowPtr(hParent) : 0;
PerformInit(parent, module);
// Install the instance window proc.
//
SubclassWindowFunction();
// Copy over the title, styles, the coordinates & the id
//
GetWindowTextTitle();
GetHWndState(true); //DLN now we force resysnc of style settings for non-owl windows.
// Keep track that this is an alias object & that it is already created
//
SetFlag(wfAlias | wfFullyCreated);
// Unique id may have been set inadvertantly to TWindow by the above
// GetWindowTextTitle, et. al. Reset it just in case
//
SetUniqueId();
}
}
//
// Private initializer function that does the bulk of the work for the
// protected Init()s
//
void
TWindow::PerformInit(TWindow* parent, TModule* module)
{
// Initialize members.
//
ZOrder = 0;
ChildList = 0;
Flags = wfDeleteOnClose;
TransferBuffer = 0;
TransferBufferSize = 0;
Parent = parent;
Attr.Param = 0;
Attr.Menu = 0;
Attr.AccelTable = 0;
HAccel = 0;
Scroller = 0;
ContextPopupMenu = 0;
Tooltip = 0;
CursorModule = 0;
CursorResId = 0;
HCursor = 0;
BkgndColor = TColor::None;
TextColor = TColor::None;
Font.reset();
// Set the window proc for this window instance.
//
InstanceProc = CreateInstanceProc();
// Link to parent.
//
if (Parent)
Parent->AddChild(this);
else
SiblingList = 0;
// Use passed module if one, else get parent's. If no parent, use app
//
if (Parent) {
Application = Parent->GetApplication();
Module = module ? module : Parent->GetModule();
}
else {
Module = module ? module : 0;
Application = TYPESAFE_DOWNCAST(Module,TApplication);
if (!Application)
{
//Application = OWLGetAppDictionary().GetApplication(0);
Application = GetApplicationObject();
if (!Module)
Module = Application;
}
CHECK(Application);
}
SetUniqueId();
TRACEX(OwlWin, 1, _T("TWindow constructed @") << (void*)this << *this);
}
namespace
{
//
// Function prototype
//
void CacheFlush(UINT_PTR id);
}
//
/// Destroys this window (unless this is an alias) and the children.
/// First, restores the underlying window's WindowProc (i.e. reverts the subclassing).
/// Then, destroys the children and, in turn, the underlying window (unless aliased).
/// Finally, dissociates this from the parent and application, and deallocates acquired resources.
//
TWindow::~TWindow()
{
// Clean up the instance WindowProc early; we're in the destructor, so we don't want to handle
// any more messages.
//
if (GetHandle())
{
WARNX(OwlWin, GetWindowProc() != GetInstanceProc(), 0,
_T("Restoring old WndProc after foreign subclass of:") << *this);
SetWindowProc(DefaultProc ? DefaultProc : &::DefWindowProc);
}
FreeInstanceProc();
InstanceProc = nullptr;
// Flush the cache, so that messages will not be dispatched to a destructed derived class.
//
owl::CacheFlush(UniqueId);
// Destroy the children first, so that they get a chance to clean up.
//
// 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 the parent is destructed.
//
auto c = vector<TWindow*>{};
for (auto& w : GetChildren())
c.push_back(&w);
for (auto w : c)
try
{
w->Destroy();
delete w;
}
catch (...)
{
TRACEX(OwlWin, 0, _T("~TWindow: Swallowed exception while destroying child: ") << *w);
}
// If the handle is still around, and this is not an alias, then destroy the window.
//
if (GetHandle() && !IsFlagSet(wfAlias))
{
WARNX(OwlWin, GetHandle(), 0, _T("Destroying from TWindow::~TWindow: ") << *this);
try { Destroy(); }
catch (...)
{
TRACEX(OwlWin, 0, _T("~TWindow: Swallowed exception while destroying this: ") << *this);
}
}
// Remove external references to this.
//
if (Parent)
Parent->RemoveChild(this);
if (Application)
Application->Uncondemn(this);
// Deallocate resources.
//
SetCaption(nullptr);
SetCursor(nullptr, TResId{0});
SetScroller(nullptr);
delete ContextPopupMenu;
if (Attr.Menu.IsString())
delete[] Attr.Menu.GetString();
TRACEX(OwlWin, 1, _T("TWindow destructed @") << (void*)this << *this);
}
#if defined(BI_MULTI_THREAD_RTL)
//
/// Cracks and dispatches a TWindow message. The info parameter is the
/// event-handling function. The wp and lp parameters are the message parameters the
/// dispatcher cracks.
/// \note Overrides TEventHandler::Dispatch() to handle multi-thread synchronization
//
TResult TWindow::Dispatch(TEventInfo& info, TParam1 p1, TParam2 p2)
{
TApplication::TQueueLock lock(*GetApplication());
return TEventHandler::Dispatch(info, p1, p2);
}
#endif
//
/// Called from TApplication::ProcessAppMsg() to give the window an
/// opportunity to perform preprocessing of the Windows message
///
/// If you return true, further processing of the message is halted
///
/// Allows preprocessing of queued messages prior to dispatching. If you override
/// this method in a derived class, be sure to call the base class's PreProcessMsg
/// because it handles the translation of accelerator keys.
//
bool
TWindow::PreProcessMsg(MSG& msg)
{
PRECONDITION(GetHandle());
// Check if this message might be worth relaying to the tooltip window
//
TTooltip* tooltip = GetTooltip();
if (tooltip && tooltip->IsWindow()) {
if (msg.hwnd == *this || IsChild(msg.hwnd)) {
if (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST) {
tooltip->RelayEvent(msg);
}
}
}
return HAccel ? ::TranslateAccelerator(GetHandle(), HAccel, &msg) : false;
}
//
/// Called when no messages are waiting to be processed, IdleAction performs idle
/// processing as long as true is returned. idleCount specifies the number of times
/// idleAction has been called between messages.
///
/// Propagate idle action to all children if count==0, and to any children that
/// previously said they wanted more time.
//
bool
TWindow::IdleAction(long idleCount)
{
bool wantMore = false;
TWindow* win = GetFirstChild();
if (win) {
do {
if (idleCount == 0 || win->IsFlagSet(wfPropagateIdle)) {
if (win->IdleAction(idleCount)) {
win->SetFlag(wfPropagateIdle);
wantMore = true;
}
else {
win->ClearFlag(wfPropagateIdle);
}
}
win = win->Next();
} while (win && win != GetFirstChild());
}
return wantMore;
}
//
/// Respond to WM_SYSCOLORCHANGE by broadcasting it to all children
//
void
TWindow::EvSysColorChange()
{
ChildBroadcastMessage(WM_SYSCOLORCHANGE);
DefaultProcessing();
}
//
/// Removes a child window. Uses the ObjectWindows list of objects rather the
/// Window's HWND list.
//
void
TWindow::RemoveChild(TWindow* child)
{
if (child && ChildList) {
TWindow* lastChild = ChildList;
TWindow* nextChild = lastChild;
while (nextChild->SiblingList != lastChild &&
nextChild->SiblingList != child) {
nextChild = nextChild->SiblingList;
}
if (nextChild->SiblingList == child) {
if (nextChild->SiblingList == nextChild)
ChildList = 0;
else {
if (nextChild->SiblingList == ChildList)
ChildList = nextChild;
nextChild->SiblingList = nextChild->SiblingList->SiblingList;
}
}
}
}
//
/// Sets the parent for the specified window by setting Parent to the specified new
/// Parent window object. Removes this window from the child list of the previous
/// parent window, if any, and adds this window to the new parent's child list.
//
void
TWindow::SetParent(TWindow* newParent)
{
if (Parent != newParent) {
if (Parent)
Parent->RemoveChild(this);
SiblingList = 0;
Parent = newParent;
if (Parent)
Parent->AddChild(this);
}
// Tell Windows about the change too, if appropriate
//
if (GetHandle() && Parent && GetParentH() != Parent->GetHandle()) {
if (newParent) {
if (newParent->GetHandle())
::SetParent(GetHandle(), newParent->GetHandle());
}
else
::SetParent(GetHandle(), 0);
}
}
//
/// Default behavior for updating document title is to pass it to parent frame
///
/// Stores the title of the document (docname). index is the number of the view
/// displayed in the document's caption bar. In order to determine what the view
/// number should be, SetDocTitle makes two passes: the first pass checks to see if
/// there's more than one view, and the second pass, if there is more than one view,
/// assigns the next number to the view. If there is only one view, index is 0;
/// therefore, the document does not display a view number. When TDocument is
/// checking to see if more than one view exists, index is -1. In such cases, only
/// the document's title is displayed in the caption bar.
/// SetDocTitle returns true if there is more than one view and TDocument displays
/// the number of the view passed in index.
//
bool
TWindow::SetDocTitle(LPCTSTR docname, int index)
{
return Parent ? Parent->SetDocTitle(docname, index) : false;
}
//
/// Sets the scroller object for this window. This window assumes ownership of the
/// scroller object, and will delete it when done and on subsequent sets.
//
void
TWindow::SetScroller(TScroller* scroller)
{
// This temporary variable is needed, because TScroller::~TScroller() calls TWindow::SetScroller(0)
TScroller* temp = Scroller;
Scroller = scroller;
delete temp;
}
//
// Wildcard check used for child id notifications
//
static bool wildcardCheck(const TGenericTableEntry & entry, const TEventHandler::TEventInfo& info)
{
return entry.Id == info.Id && (entry.Msg == UINT_MAX || entry.Msg == info.Msg);
}
//
/// Handles default processing of events, which includes continued processing
/// of menu/accelerators commands and enablers, as well as notifications
//
/// Serves as a general-purpose default processing function that handles a variety
/// of messages. After being created and before calling DefaultProcessing, however,
/// a window completes the following sequence of events (illustrated in the Default
/// Message-Processing Flowchart).
/// - If the window is already created, SubclassWindowFunction is used to
/// install a thunk in place of the window's current procedure. The previous
/// window procedure is saved in DefaultProc.
/// - If the window has not been created, InitWndProc is set up as the
/// window proc in the class. Then, when the window first receives a message,
/// InitWndProc calls GetWindowProc to get the window's thunk (created by the
/// constructor by calling CreateInstanceProc). InitWndProc then switches the
/// the window procedure to the thunk.
/// - After this point, the thunk responds to incoming messages by calling ReceiveMessage
/// which calls the virtual function WindowProc to process the message. ObjectWindows uses
/// the special registered message GetWindowPtrMsgId to get the this-pointer of an HWND.
/// ReceiveMessage responds to this message by returning the this pointer obtained
/// from the thunk.
///
/// If the incoming message is not a command or command enable message, WindowProc
/// immediately searches the window's response table for a matching entry. If the
/// incoming message is a command or command enable message, WindowProc calls
/// EvCommand or EvCommandEnable. EvCommand and EvCommandEnable begin searching for
/// a matching entry in the focus window's response table. If an entry is found, the
/// corresponding function is dispatched; otherwise ObjectWindows calls
/// DefaultProcessing to finish the recursive walk back up the parent chain,
/// searching for a match until the receiving window (the window that initially
/// received the message) is reached. At this point, one of the following actions
/// occurs:
/// - If there is still no match and this is the MainWindow of the
/// application, the window searches the application's response table.
/// - If there are no matches and this is a command, DefWindowProc is
/// called.
/// - If this is a CommandEnable message, no further action is taken.
/// - If this is not a command, and if a response table entry exists for
/// the window, WindowProc dispatches the corresponding EvXxxx function to handle
/// the message.
/// - If this is the application's MainWindow, and the message is
/// designed for the application, the message is forwarded to the application.
/// - For any other cases, the window calls DefWindowProc.
///
/// The following diagram illustrates this sequence of message-processing events:
/// \image html bm282.BMP
//
TResult
TWindow::DefaultProcessing()
{
TCurrentEvent& currentEvent = GetCurrentEvent();
if (currentEvent.Message == WM_COMMAND_ENABLE) {
if (currentEvent.Win != this) {
TWindow* receiver = Parent ? Parent : currentEvent.Win;
TCommandEnabler& commandEnabler = *(TCommandEnabler*)currentEvent.Param2;
TEventInfo eventInfo(WM_COMMAND_ENABLE, commandEnabler.GetId());
if (receiver->Find(eventInfo))
return receiver->Dispatch(eventInfo, 0, currentEvent.Param2);
}
return 0;
}
// Handle normal message default processing by routing directly to the
// virtual DefWindowProc() for the window.
//
if (currentEvent.Message != WM_COMMAND && currentEvent.Message != WM_NOTIFY)
return DefWindowProc(currentEvent.Message,
currentEvent.Param1,
currentEvent.Param2);
// currentEvent.Message == WM_COMMAND or WM_NOTIFY
//
uint notifyCode;
uint id;
HWND hWndCtl;
// Get notify code, control id and control handle from packed params.
// Unpacking based on message & platform.
//
if (currentEvent.Message == WM_COMMAND) {
notifyCode = HiUint16(currentEvent.Param1);
id = LoUint16(currentEvent.Param1);
hWndCtl = reinterpret_cast<HWND>(currentEvent.Param2);
}
else {
TNotify& _not = *(TNotify*)currentEvent.Param2;
notifyCode = _not.code;
id = static_cast<uint>(_not.idFrom); //currentEvent.Param1; // Y.B. In help written -> use not.idFrom.
if (_not.code == (uint)TTN_NEEDTEXT &&
((TTooltipText*)&_not)->uFlags&TTF_IDISHWND){
id = ::GetDlgCtrlID((HWND)_not.idFrom);
}
hWndCtl = _not.hwndFrom;
}
// If all special routing is done, then process the command/notify for this
// window
//
if (currentEvent.Win == this) {
// Menu command originally destined for the receiver, defer to the app.
//
if (hWndCtl == 0) {
TEventInfo eventInfo(0, id);
TApplication* app = GetApplication();
if (app->Find(eventInfo)) {
app->Dispatch(eventInfo, eventInfo.Id);
return 0;
}
WARNX(OwlMsg, notifyCode<=1, 0, _T("Unprocessed WM_COMMAND(id=") << id << _T(")"));
}
// Originally destined for the receiver and the receiver has called us so
// default processing can occur.
// Unpack the original parameters and call DefWindowProc()
//
return DefWindowProc(currentEvent.Message, currentEvent.Param1, currentEvent.Param2);
}
// Perform special routing for commands and notifications
//
else {
TWindow* receiver;
TEqualOperator equal = 0;
if (hWndCtl == 0) {
// Menu/accelerator/push button
// Either give the message to the receiver's parent or the original
// receiver of the message
//
receiver = Parent ? Parent : currentEvent.Win;
// "notifyCode" is either 0 or 1 depending on whether this is from an
// accelerator; however, we want to explicitly look for a 0...
//
notifyCode = 0;
}
else {
// Child id notification (either WM_COMMAND or WM_NOTIFY) sent to the
// child and the child has called us.
// Offer the parent an opportunity to handle the notification
// NOTE: use equal function that will do a wildcard search
//
equal = wildcardCheck;
receiver = currentEvent.Win;
// The child window identifier shouldn't be 0, but if it is then it will
// look like we are searching for a WM_ message with value "notifyCode",
// in that case just give up and call DefWindowProc() for the window.
//
if (receiver->IsFlagSet(wfAlias) || id == 0)
return receiver->DefWindowProc(currentEvent.Message,
currentEvent.Param1,
currentEvent.Param2);
}
// Now dispatch the command or notification to the receiver window
//
TEventInfo eventInfo(notifyCode, id);
// NOTE: The ResponseTableEntry of handlers which expect an id (e.g.
// EV_COMMAND_AND_ID) have a zero in their first field. The
// ResponseTableEntry of handlers which expect a notification code
// (e.g. EV_CHILD_NOTIFY_AND_CODE and EV_NOTIFY_ALL_CODES) contain
// either the notifyCode or UINT_MAX in that field. Hence, we can
// dispatch the expected information based on the contents of that
// field.
//
// However, this logic will fail to disambiguate cases where a
// notification code is 0. The only standard control with such a
// notification is BUTTON /w BN_CLICKED. So for button handlers
// expecting the id, you can use EV_COMMAND_AND_ID. For handlers
// expecting the notification code, you can use EV_NOTIFY_ALL_CODES,
// (*NOT* EV_CHILD_NOTIFY_AND_CODE(Id, BN_CLICKED, xxxx,...))
//
if (receiver->Find(eventInfo, equal)) {
TParam1 param1 = eventInfo.Entry->NotifyCode ? notifyCode : id;
return receiver->Dispatch(eventInfo, param1, currentEvent.Param2);
}
else
return receiver->DefaultProcessing();
}
}
//
/// WindowProc calls EvCommand to handle WM_COMMAND messages. id is the identifier
/// of the menu item or control. hWndCtl holds a value that represents the control
/// sending the message. If the message is not from a control, it is 0. notifyCode
/// holds a value that represents the control's notification message. If the message
/// is from an accelerator, notifyCode is 1; if the message is from a menu,
/// notifyCode is 0.
//
TResult
TWindow::EvCommand(uint id, HWND hWndCtl, uint notifyCode)
{
TRACEX(OwlCmd, 1, _T("TWindow::EvCommand - id(") << id << _T("), ctl(") <<\
hex << reinterpret_cast<void*>(hWndCtl) << _T("), code(") << notifyCode << _T(")"));
TWindow* receiver = this;
TEqualOperator equal = 0;
// Menu/accelerator
//
if (hWndCtl == 0) {
// "notifyCode" is either 0 or 1 depending on whether this is from an
// accelerator; however, we want to explicitly look for a 0 in the tables
//
notifyCode = 0;
}
// Child id notification
//
else {
TWindow* child = GetWindowPtr(hWndCtl);
if (child) {
// Give the child first crack at the event
//
receiver = child;
id = UINT_MAX; // special designator for child Id notifications that are
// handled at the child
}
else {
// Offer the parent an opportunity to handle the notification
//
// NOTE: use equal function that will do a wildcard search
//
equal = wildcardCheck;
// The child window identifier shouldn't be 0, but if it is then it will
// look like we are searching for a WM_ message with value "notifyCode"
//
if (id == 0)
return DefaultProcessing();
}
}
TEventInfo eventInfo(notifyCode, id);
// NOTE: The ResponseTableEntry of handlers which expect an id
// (e.g. EV_COMMAND_AND_ID) have a zero in their first field.
// The ResponseTableEntry of handlers which expect a notification
// code (e.g. EV_CHILD_NOTIFY_AND_CODE and EV_NOTIFY_ALL_CODES)
// contain either the notifyCode or UINT_MAX in that field.
// Hence, we can dispatch the expected information based on the
// contents of that field.
//
// However, this logic will fail to disambiguate cases where
// a notification code is 0. The only standard control with
// such a notification is BUTTON /w BN_CLICKED. So for button
// handlers expecting the id, you can use EV_COMMAND_AND_ID.
// For handlers expecting the notification code, you can use
// EV_NOTIFY_ALL_CODES.
//
// Do *NOT* use "EV_CHILD_NOTIFY_AND_CODE(Id, BN_CLICKED, xxxx,...)"
//
if (receiver->Find(eventInfo, equal))
return receiver->Dispatch(eventInfo,
eventInfo.Entry->NotifyCode ? notifyCode : id);
else
return receiver->DefaultProcessing();
}
//
/// Handles WM_NOTIFY and subdispatch messages from child controls. This is the
/// default message handler for WM_NOTIFY.
// !CQ NOTE: similarity between EvCommand child notifies--merge?
//
TResult
TWindow::EvNotify(uint ctlId, TNotify& notifyInfo)
{
// Intercept requests for tooltip texts and turn the request into
// a command enabler. This mechanism allows us to route the request
// the same way commands are routed in OWL. Therefore, the object that
// handles a command is the object that gets first crack at providing
// the tip text for that command.
//
// We also handle a special case here, where TTN_NEEDTEXT may put a HWND in NHMDR::idFrom, in
// which case ctlId will also be a HWND in disguise. If so, we detect it here and convert the
// handle in ctlId back to the true control ID, so that we can do proper response table lookup
// later on, if the command enabler lookup here is unsuccessful.
//
if (notifyInfo.code == TTN_NEEDTEXT)
{
auto& ttText = reinterpret_cast<TTooltipText&>(notifyInfo);
if (ttText.uFlags & TTF_IDISHWND)
ctlId = ::GetDlgCtrlID(reinterpret_cast<HWND>(ttText.hdr.idFrom));
auto enabler = TTooltipEnabler{ttText, GetHandle()};
const auto cmdTarget = GetParent() ? GetParent() : this;
RouteCommandEnable(cmdTarget->GetHandle(), enabler);
const auto handled = enabler.GetHandled();
WARNX(OwlWin, !handled, 1, _T("No command enabler found for TTN_NEEDTEXT (Id: ") << enabler.GetId() << _T(")."));
if (handled)
return 0;
}
TWindow* receiver = this;
TEqualOperator equal = 0;
TWindow* child = GetWindowPtr(notifyInfo.hwndFrom);
if (child) {
// Give the Owl child first crack at the event
//
receiver = child;
ctlId = UINT_MAX;// special designator for child Id notifications that are
// handled at the child // !CQ may need a different value
}
else {
// Offer the parent an opportunity to handle the notification
// NOTE: use equal function that will do a wildcard search
//
equal = wildcardCheck;
// The child window identifier shouldn't be 0, but if it is then it will
// look like we are searching for a WM_ message with value "notifyCode"
//
if (ctlId == 0)
return DefaultProcessing();
}
TEventInfo eventInfo(notifyInfo.code, ctlId);
// Pass the "notifyCode" along in case the user wants it...
//
if (receiver->Find(eventInfo, equal))
return receiver->Dispatch(eventInfo, notifyInfo.code, TParam2(¬ifyInfo));
else
return receiver->DefaultProcessing();
}
//
/// Called by WindowProc to handle WM_COMMAND_ENABLE messages, EvCommandEnable calls
/// the CmXxxx command-handling function or calls DefaultProcessing to handle the
/// incoming message.
//
void
TWindow::EvCommandEnable(TCommandEnabler& commandEnabler)
{
// code copied from old unix owl (JAM 04-16-01)
//DLN test replace of DispatchMsg for CC 5.1
TEventInfo eventInfo(WM_COMMAND_ENABLE,commandEnabler.GetId());
if (Find(eventInfo))
Dispatch(eventInfo,0,TParam2(&commandEnabler));
// DispatchMsg(WM_COMMAND_ENABLE, commandEnabler.Id, 0, TParam2(&commandEnabler));
}
//
/// Walks the chain of windows from the initial target window to this window. If it
/// finds a window to receive the message, RouteCommandEnable dispatches the command
/// enabler to that window. hInitCmdTarget is the handle to the initial command
/// target window, which can be focus window but does not need to be. ce is a
/// reference to the command enabler.
/// Other classes use this function to perform particular command enabling tasks:
/// For example, TFrameWindow calls RouteCommandEnable to perform the majority of
/// its menu command enabling tasks. When it is an embedded window, TOleWindow also
/// uses RouteCommandEnable to perform command enabling.
///
/// Don't process for windows out of our window tree (esp. other apps)
//
void
TWindow::RouteCommandEnable(HWND hInitCmdTarget, TCommandEnabler& commandEnabler)
{
// Extra processing for commands & commandEnablers: send the command down the
// command chain if we are the original receiver
//
if (commandEnabler.IsReceiver(*this)) {
HWND hCmdTarget = hInitCmdTarget;
while (hCmdTarget && hCmdTarget != *this) {
TWindow* cmdTarget = GetWindowPtr(hCmdTarget);
if (cmdTarget) {
cmdTarget->EvCommandEnable(commandEnabler);
if (commandEnabler.GetHandled())
return;
}
hCmdTarget = ::GetParent(hCmdTarget);
}
}
// Always call base handler
//
TWindow::EvCommandEnable(commandEnabler);
// No one explicitly enabled/disabled the command via the enabler, so run up
// the command chain checking for any one who is going to handle the command
// itself; if not then disable it...
// Don't do this for command senders that don't actually generate commands,
// like popup menu items.
//
if (commandEnabler.IsReceiver(*this) && !commandEnabler.GetHandled()
&& commandEnabler.SendsCommand()) {
bool enable = false;
TEventInfo eventInfo(0, commandEnabler.GetId());
HWND hCmdTarget = hInitCmdTarget;
while (true) {
TWindow* cmdTarget = GetWindowPtr(hCmdTarget);
if (cmdTarget && cmdTarget->Find(eventInfo)) {
enable = true; // command will be handled, leave sender alone
break;
}
if (!hCmdTarget || hCmdTarget == *this)
break;
hCmdTarget = ::GetParent(hCmdTarget);
}
if (!enable) {
// Check if the app wants to handle it
//
TEventInfo enableInfo(WM_COMMAND_ENABLE, commandEnabler.GetId());
TApplication* app = GetApplication();
if (app->Find(enableInfo)) {
app->Dispatch(enableInfo, 0, TParam2(&commandEnabler));
if (commandEnabler.GetHandled())
return;
}
enable = app->Find(eventInfo);
}
commandEnabler.Enable(enable);
}
}
//
/// Virtual function provides final default processing for an incoming message
/// Calls original window proc that was subclassed, using ::%CallWindowProc to
/// make sure that registers get setup correctly.
//
/// Performs default Windows processing and passes the incoming Windows message. You
/// usually do not need to call this function directly. Classes such as TMDIFrame
/// and TMDIChild override this function to perform specialized default processing.
TResult
TWindow::DefWindowProc(TMsgId message, TParam1 param1, TParam2 param2)
{
PRECONDITION(DefaultProc);
bool priorException = Application && Application->HasSuspendedException();
TResult result = ::CallWindowProc(DefaultProc, GetHandle(), message, param1, param2);
if (!priorException) // Don't rethrow exceptions if we are in a clean-up phase.
GetApplication()->ResumeThrow();
return result;
}
//namespace owl {
/// \addtogroup internal_group
/// @{
//
// Message cache
//
static const int msgCacheSize = 31;
struct TCacheEntry{
typedef UINT_PTR TId;
TId UniqueId;
const TGenericTableEntry* Entry;
TMsgId Msg;
int Delta; // adjustment to "this" pointer
//30.05.2007 - Submitted by Frank Rast: Initialization of TCacheEntry data members was missing.
TCacheEntry() : UniqueId(0), Entry(0), Msg(0), Delta(0) {}
void Set(TId uniqueId, TMsgId msg, const TGenericTableEntry* entry, int delta = 0) {
UniqueId = uniqueId;
Entry = entry;
Msg = msg;
Delta = delta;
}
bool Hit(TMsgId msg, TId uniqueId) {return msg == Msg && uniqueId == UniqueId;}
static int Key(TId id, TMsgId msg) {return (id ^ static_cast<TId>(msg)) % msgCacheSize;}
};
struct TCacheEntryStr
#if defined(BI_MULTI_THREAD_RTL)
: public TLocalObject
#endif
{
TCacheEntryStr():Enabled(true)
{
}
~TCacheEntryStr()
{
}
TCacheEntry Cache[msgCacheSize];
bool Enabled;
void CacheFlush(TCacheEntry::TId id);
void Set(int index, TCacheEntry::TId uniqueId, TMsgId, const TGenericTableEntry* entry, int delta = 0);
bool Check(int index, TMsgId, TCacheEntry::TId id);
#if defined(BI_MULTI_THREAD_RTL)
// TMRSWSection Lock;
#endif
};
TCacheEntry::TId TWindow::LastUniqueId = 0;
static TCacheEntryStr& GetCache()
{
#if defined(BI_MULTI_THREAD_RTL)
static TTlsContainer<TCacheEntryStr> cacheEntry;
return cacheEntry.Get();
#else
static TCacheEntryStr cacheEntry;
return cacheEntry;
#endif
};
namespace
{
//
// Ensure singleton initialization at start-up (single-threaded, safe).
//
TCacheEntryStr& InitCacheEntryStr = GetCache();
}
#if defined(BI_MULTI_THREAD_RTL)
#define LOCKCACHE //TMRSWSection::TLock Lock(GetCache().Lock);
#else
#define LOCKCACHE
#endif
//
void TCacheEntryStr::CacheFlush(TCacheEntry::TId id)
{
LOCKCACHE
for (int i = 0; i < msgCacheSize; i++)
if (Cache[i].UniqueId == id)
Cache[i].Msg = 0;
}
//
void TCacheEntryStr::Set(int index, TCacheEntry::TId uniqueId, TMsgId msg, const TGenericTableEntry* entry, int delta)
{
LOCKCACHE
Cache[index].Set(uniqueId, msg, entry, delta);
}
//
bool TCacheEntryStr::Check(int index, TMsgId msg, TCacheEntry::TId id)
{
LOCKCACHE
return Enabled && Cache[index].Hit(msg, id);
}
namespace
{
//
void CacheFlush(UINT_PTR id)
{
GetCache().CacheFlush(id);
}
}
/// @}
//
// If rtti is available, then use it get an id for this window that is unique
// on a per-class basis.
//
// Without rtti, use a per-instance unique id. Less efficient, but safe.
//
void
TWindow::SetUniqueId()
{
#if defined(OWL_RTTI_MSGCACHE)
UniqueId = 0;
#else
if (++LastUniqueId == 0) {
//
// numbers wrapped around. disable the cache to be safe...
//
LastUniqueId = 1;
GetCache().Enabled = false;
}
UniqueId = LastUniqueId;
#endif
}
//
/// Dispatches the given message using the response table. Similar to SendMessage
/// but goes directly to the OWL window, bypassing the Windows message queue.
//
TResult
TWindow::HandleMessage(TMsgId msg, TParam1 p1, TParam2 p2)
{
// ReceiveMessage suspends any exception, so we need to rethrow it after the call.
//
TResult r = ReceiveMessage(Handle, msg, p1, p2);
GetApplication()->ResumeThrow();
return r;
}
//
/// First virtual function called to handling incoming messages to a TWindow
//
/// Processes incoming messages by calling EvCommand to handle WM_COMMAND messages,
/// EvCommandEnable to handle WM_COMMAND_ENABLE messages, and dispatching for all
/// other messages.
//
TResult
TWindow::WindowProc(TMsgId msg, TParam1 param1, TParam2 param2)
{
PRECONDITION(GetHandle()); // Should never get here without a handle
// Handle WM_COMMAND_ENABLE command enable msgs by directly calling the
// virtual EvCommandEnable
//
if (msg == WM_COMMAND_ENABLE) {
TRACEX(OwlMsg, 1, _T("WM_COMMAND_ENABLE") << _T("! => ") << *this);
EvCommandEnable(*(TCommandEnabler*)param2);
return 0;
}
// Handle WM_COMMAND command msgs by directly calling the
// virtual EvCommand
//
else if (msg == WM_COMMAND) {
TRACEX(OwlMsg, 1, _T("WM_COMMAND, ") << LoUint16(param1) << _T(" ! => ") << *this);
return EvCommand(LoUint16(param1), reinterpret_cast<HWND>(param2), HiUint16(param1));
}
// Handle WM_NOTIFY msgs by directly calling the virtual EvNotify
// !CQ why not use response table dispatch? For above too?
//
else if (msg == WM_NOTIFY) {
// We've received reports of some controls (out there) sending
// WM_NOTIFY with a NULL LPARAM !!
//
TNotify dumbNMHDR;
//DLN SET ID to 0 for these NULL LPARAM to avoid bad resource access
dumbNMHDR.idFrom = static_cast<UINT>(-1);
TNotify* notify = param2 ? (TNotify*)param2 : &dumbNMHDR;
TRACEX(OwlMsg, 1, _T("WM_NOTIFY, ") << notify->idFrom << _T(", ")\
<< notify->code << _T(", ")\
<< hex << reinterpret_cast<void*>(notify->hwndFrom) << _T(", ")\
<< _T(" ! => ") << *this);
return EvNotify(static_cast<uint>(param1), *notify);
}
// Handle all other messages by looking up and dispatching using the
// response tables
//
else {
#if defined(OWL_RTTI_MSGCACHE)
if (!UniqueId)
UniqueId = TYPE_UNIQUE_ID(*this);
#endif
int key = TCacheEntry::Key(UniqueId, msg);
TEventInfo eventInfo(msg);
// Check the cache. A cache hit may be an RT handler, or an RT miss.
//
TCacheEntryStr& cache = GetCache();
if(cache.Check(key, msg, UniqueId)) {
eventInfo.Entry = cache.Cache[key].Entry;
if (eventInfo.Entry) {
TRACEX(OwlMsg, 1, TMsgName(msg) << _T("* => ") << *this);
eventInfo.Object = (TGeneric*)(((char*)this) + cache.Cache[key].Delta);
return Dispatch(eventInfo, param1, param2);
} // else fall out & do default below
}
// Perform the lookup on this window.
//
else if (this->Find(eventInfo)) {
TRACEX(OwlMsg, 1, TMsgName(msg) << _T(" => ") << *this);
const int delta = static_cast<int>(reinterpret_cast<INT_PTR>(eventInfo.Object) - reinterpret_cast<INT_PTR>(this));
cache.Set(key,UniqueId, msg, eventInfo.Entry, delta);
return Dispatch(eventInfo, param1, param2);
}
else // not found
cache.Set(key,UniqueId, msg, 0); // Cache no-handler entries too
// Behavior for messages that have no handler. If this is the main window,
// then give the app a chance to handle the message. If not the main
// window, or if the app had no handler, just call DefWindowProc() to
// pass the message back to whomever we subclassed
//
if (IsFlagSet(wfMainWindow)) {
TEventInfo cmdEventInfo(msg, static_cast<uint>(param1));
if (GetApplication()->Find(cmdEventInfo))
return GetApplication()->Dispatch(cmdEventInfo, param1, param2);
if (GetApplication()->Find(eventInfo))
return GetApplication()->Dispatch(eventInfo, param1, param2);
}
return DefWindowProc(msg, param1, param2);
}
}
#if !defined(BI_COMP_GNUC)
#pragma warn -par
#endif
//
/// Handles messages sent to the window.
/// May be called directly by the windows message dispatch mechanism,
/// or manually via HandleMessage.
/// If the message is a GetWindowPtr message, it is handled immediately,
/// otherwise the current event is saved and the call forwarded to WindowProc.
/// Any unhandled exception in WindowProc is suspended (see TApplication::SuspendThrow).
//
TResult
TWindow::ReceiveMessage(HWND hwnd, TMsgId msg, TParam1 param1, TParam2 param2) noexcept
{
WARNX(OwlWin, hwnd != Handle, 0, "ReceiveMessage: The passed handle does not match this window.");
InUse(hwnd);
// If it's a "GetWindowPtr" message, then return pointer to this window
//
if (msg == GetWindowPtrMsgId && (!param2 || param2 == TParam2(Application)))
return TResult(this);
// Save event away in "CurrentEvent"
//
TCurrentEvent& currentEvent = GetCurrentEvent();
TCurrentEvent saveEvent = currentEvent; // for nested calls
currentEvent.Win = this;
currentEvent.Message = msg;
currentEvent.Param1 = param1;
currentEvent.Param2 = param2;
// Call window object's WindowProc virtual function
//
TResult result = 0;
try
{
result = WindowProc(msg, param1, param2);
}
catch (...)
{
TRACEX(OwlWin, 0, _T("TWindow::ReceiveMessage: Suspending unhandled exception for message: ") << msg);
GetApplication()->SuspendThrow(std::current_exception());
}
currentEvent = saveEvent; // restore previous event to current event
return result;
}
#if !defined(BI_COMP_GNUC)
#pragma warn .par
#endif
//
/// Determine the object pointer by sending a GetWindowPtrMsgId message to the window.
/// When TWindow::ReceiveMessage receives this message it returns a pointer to the object.
/// If app is non-zero, then the process makes sure that the corresponding
/// TWindow is returned.
//
_OWLFUNC(TWindow*) GetWindowPtr(HWND hWnd, const TApplication* app)
{
if (!hWnd /* && ::IsWindow(hWnd) */) // Could also check handle validity
return 0;
TParam2 param2 = TParam2(app);
// Avoid SendMessage to cutdown the overhead & message spy tool flooding
//
// Under Win32 don't even try if it is not our process. Some Win32's will
// return a wndProc that crashes.
//
DWORD processId;
::GetWindowThreadProcessId(hWnd, &processId);
if (processId != ::GetCurrentProcessId())
return 0;
WNDPROC wndProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC);
if (!wndProc)
return 0;
return (TWindow*)::CallWindowProc(wndProc, hWnd, GetWindowPtrMsgId, 0, param2);
}
//
/// Response method for an incoming WM_CREATE message
///
/// Performs setup and data transfer now that we are created & have a Handle.
/// Should return true if the
//
bool
TWindow::EvCreate(CREATESTRUCT &)
{
PerformSetupAndTransfer(); // TODO: Add exception handling and return false on exceptions.
TResult r = DefaultProcessing();
return r == 0;
}
//
/// Responds to a request by a child window to hold its HWND when it is losing
/// focus. Stores the child's HWND in HoldFocusHwnd.
//
/// \note Regular windows never hold focus child handles--just say no.
//
bool
TWindow::HoldFocusHWnd(HWND /*hWndLose*/, HWND /*hWndGain*/)
{
return false;
}
//
/// Handle WM_KILLFOCUS so that we can have a parent window hold onto our
/// Handle and possibly restore focus later.
//
void
TWindow::EvKillFocus(HWND getFocus)
{
// Follow the parent chain back until a window volunteers to hold our handle
//
if (IsFlagSet(wfFullyCreated)) {
TWindow* holdWin = Parent;
while (holdWin && !holdWin->HoldFocusHWnd(GetHandle(), getFocus))
holdWin = holdWin->Parent;
}
DefaultProcessing();
}
//
/// Response method for an incoming WM_SIZE message
///
/// Saves the normal size of the window in Attr.
/// Also calls the SetPageSize() and SetBarRange() methods of the TWindow's
/// scroller, if it has been constructed.
//
void
TWindow::EvSize(uint sizeType, const TSize&)
{
TraceWindowPlacement();
static bool inScroller = false;
if (!inScroller && Scroller && sizeType != SIZE_MINIMIZED) {
inScroller = true;
Scroller->SetPageSize();
Scroller->SetSBarRange();
inScroller = false;
}
if (sizeType == SIZE_RESTORED) {
TRect wndRect;
GetWindowRect(wndRect);
Attr.W = wndRect.Width();
Attr.H = wndRect.Height();
}
// Added Owl functionality: notify parent of a resize in case it wants to
// adjust accordingly
//
if (Parent && !(GetExStyle() & WS_EX_NOPARENTNOTIFY))
Parent->SendMessage(WM_PARENTNOTIFY, WM_SIZE, TParam2(GetHandle()));
DefaultProcessing();
}
//
/// Save the normal position of the window.
/// If IsIconic() or IsZoomed() ignore the position since it does not reflect
/// the normal state
//
void
TWindow::EvMove(const TPoint&)
{
if (!IsIconic() && !IsZoomed()) {
TRect wndRect;
GetWindowRect(wndRect);
if ((GetWindowLong(GWL_STYLE) & WS_CHILD) == WS_CHILD && Parent &&
Parent->GetHandle())
Parent->ScreenToClient(wndRect.TopLeft());
Attr.X = wndRect.left;
Attr.Y = wndRect.top;
}
DefaultProcessing();
}
//
/// Handles WM_COMPAREITEM message (for owner draw controls) by forwarding
/// message to control itself
//
int
TWindow::EvCompareItem(uint /*ctrlId*/, const COMPAREITEMSTRUCT& compareInfo)
{
TWindow* w = GetWindowPtr(compareInfo.hwndItem);
TResult r = w ? w->ForwardMessage() : DefaultProcessing();
return static_cast<int>(r);
}
//
/// Handles WM_DELETEITEM message (for owner draw controls) by forwarding
/// message to control itself
//
void
TWindow::EvDeleteItem(uint /*ctrlId*/, const DELETEITEMSTRUCT& deleteInfo)
{
TWindow* win = GetWindowPtr(deleteInfo.hwndItem);
if (deleteInfo.hwndItem != GetHandle() && win)
win->ForwardMessage();
else
DefaultProcessing();
}
//
/// Handles WM_DRAWITEM message (for owner draw controls & menus) by forwarding
/// message to control itself
//
TDrawItem* ItemData2DrawItem(ULONG_PTR data);
void
TWindow::EvDrawItem(uint /*ctrlId*/, const DRAWITEMSTRUCT& drawInfo)
{
if (drawInfo.CtlType == ODT_MENU) {
TDrawItem* item = ItemData2DrawItem(drawInfo.itemData);
if(item){
DRAWITEMSTRUCT i(drawInfo); // Create copy to support legacy non-const virtual TDrawItem::Draw.
item->Draw(i);
return;
}
}
else {
TWindow* win = GetWindowPtr(drawInfo.hwndItem);
if (drawInfo.hwndItem != GetHandle() && win) {
win->ForwardMessage();
return;
}
}
DefaultProcessing();
}
//
/// Handles WM_MEASUREITEM message (for owner draw controls & menus) by
/// forwarding message to control itself
//
void
TWindow::EvMeasureItem(uint ctrlId, MEASUREITEMSTRUCT & measureInfo)
{
if (measureInfo.CtlType == ODT_MENU) {
TDrawItem* item = ItemData2DrawItem(measureInfo.itemData);
if(item){
item->Measure(measureInfo);
return;
}
}
else {
HWND hCtl = GetDlgItem(measureInfo.CtlID); // hWnd not in struct, get
TWindow* win = GetWindowPtr(hCtl);
// If the GetWindowPtr failed, & CreationWindow is non-zero, then this
// WM_MEASUREITEM is probably for the ctrl which is not yet subclassed.
// Route the message directly to creation window.
// NOTE: Msg. may be sent before OWL has had a chance to subclass the ctrl.
//
if (!win) {
TWindow* creationWindow = GetCreationWindow();
if (creationWindow) {
if (creationWindow != this) // Don't cause a recursion loop
win = creationWindow;
}
else
win = ChildWithId(ctrlId);
}
if(win){
// 970921 MILI 11 The first WM_MEASUREITEM message for a control may
// be is sent before OWL has had any chance to grab the handle for that
// control. In that case we use Find+Dispatch instead of SendMessage to
// forward the message to the control.
if(win->GetHandle()){
// !CQ Handle causes bad behavior on DefWindowProc of control in some cases
/// win->HandleMessage(WM_MEASUREITEM, ctrlId, TParam2(&measureInfo));
/// win->ForewardMessage();
win->SendMessage(WM_MEASUREITEM, ctrlId, TParam2(&measureInfo));
return;
}
else{
TEventInfo eventInfo(WM_MEASUREITEM);
if (win->Find(eventInfo)){
win->Dispatch(eventInfo, ctrlId, TParam2(&measureInfo));
return;
}
}
}
}
DefaultProcessing();
}
//
/// Called by EvHScroll and EvVScroll to dispatch messages from scroll bars.
//
void
TWindow::DispatchScroll(uint scrollCode, uint /*thumbPos*/, HWND hWndCtrl)
{
if (hWndCtrl) {
TWindow* win = GetWindowPtr(hWndCtrl);
if (win)
win->ForwardMessage();
// Adjust "CurrentEvent" to allow DefaultProcessing to work
//
uint16 id = (uint16)::GetDlgCtrlID(hWndCtrl);
TCurrentEvent& currentEvent = GetCurrentEvent();
currentEvent.Message = WM_COMMAND;
currentEvent.Param1 = MkParam1(id, scrollCode);
currentEvent.Param2 = TParam2(hWndCtrl);
EvCommand(id, hWndCtrl, scrollCode);
return;
}
DefaultProcessing();
}
//
/// Event handler for WM_MOUSEHWHEEL.
/// Responds to horizontal mouse wheel event if Scroller exists.
//
void
TWindow::EvMouseHWheel(uint modKeys, int zDelta, const TPoint& /*point*/)
{
if (Scroller && !(modKeys & (MK_SHIFT | MK_CONTROL)))
{
const auto n = GetWheelScrollLines_();
const auto dx = (n == WHEEL_PAGESCROLL) ?
((zDelta > 0) ? -Scroller->XPage : Scroller->XPage) :
::MulDiv(-zDelta, n, WHEEL_DELTA) * Scroller->XLine;
Scroller->ScrollBy(dx, 0);
}
else
DefaultProcessing();
}
//
/// Event handler for WM_MOUSEWHEEL.
/// Responds to vertical mouse wheel event if Scroller exists.
//
void
TWindow::EvMouseWheel(uint modKeys, int zDelta, const TPoint& /*point*/)
{
if (Scroller && !(modKeys & (MK_SHIFT | MK_CONTROL)))
{
const auto n = GetWheelScrollLines_();
const auto dy = (n == WHEEL_PAGESCROLL) ?
((zDelta > 0) ? -Scroller->YPage : Scroller->YPage) :
::MulDiv(-zDelta, n, WHEEL_DELTA) * Scroller->YLine;
Scroller->ScrollBy(0, dy);
}
else
DefaultProcessing();
}
//
/// Response method for an incoming WM_HSCROLL message
///
/// If the message is from a scrollbar control, calls DispatchScroll()
/// otherwise passes the message to the TWindow's scroller if it has been
/// constructed, else calls DefaultProcessing()
///
/// Assumes, because of a Windows bug, that if the window has the scrollbar
/// style, it will not have scrollbar controls
//
void
TWindow::EvHScroll(uint scrollCode, uint thumbPos, HWND hWndCtl)
{
if (!(GetWindowLong(GWL_STYLE) & WS_HSCROLL) && !Scroller)
{
DispatchScroll(scrollCode, thumbPos, hWndCtl); // from scrollbar control
}
else if (Scroller)
{
Scroller->HScroll(scrollCode, thumbPos);
}
else
{
DefaultProcessing();
}
}
//
/// Response method for an incoming WM_VSCROLL message
///
/// If the message is from a scrollbar control, calls DispatchScroll()
/// otherwise passes the message to the TWindow's scroller if it has been
/// constructed, else calls DefaultProcessing()
///
/// Assumes, because of a Windows bug, that if the window has the scrollbar
/// style, it will not have scrollbar controls
//
void
TWindow::EvVScroll(uint scrollCode, uint thumbPos, HWND hWndCtl)
{
if (!(GetWindowLong(GWL_STYLE) & WS_VSCROLL) && !Scroller)
DispatchScroll(scrollCode, thumbPos, hWndCtl);
else if (Scroller)
Scroller->VScroll(scrollCode, thumbPos);
else
DefaultProcessing();
}
//
/// Handler for WM_ERASEBKGND.
///
/// If the background color (see TWindow::SetBkgndColor) is not set, or
/// it has been set to TColor::None, then this function simply forwards the
/// message to DefaultProcessing. The default processing uses the brush
/// specified in the window class (WNDCLASS:hbrBackground) to clear the client
/// area. If the background color is set to TColor::Transparent, no clearing is
/// done, and the function simply returns `true`. Otherwise, the current
/// background color is used to clear the client area, and `true` is returned,
/// indicating no further erasing is required.
///
/// If you want to handle erasure in your Paint override, then override this
/// handler and return false. Paint will then receive `true` in its `erase`
/// parameter, and you must ensure that all of the background is painted within
/// the invalidated region.
///
/// http://msdn.microsoft.com/en-us/library/windows/desktop/ms648055.aspx
//
bool
TWindow::EvEraseBkgnd(HDC hDC)
{
if (BkgndColor == TColor::None)
return static_cast<bool>(DefaultProcessing()); // Use WNDCLASS::hbrBackground.
if (BkgndColor == TColor::Transparent)
return true; // We want no erasing, please.
TDC(hDC).FillSolidRect(GetClientRect(), BkgndColor);
return true;
}
//
/// Handler for control color messages (WM_CTLCOLOR family).
///
/// If the passed control `ctl` is a child encapsulated by OWL, then the text and background color
/// of the control is used. If the control is not encapsulated by OWL, or the control does not
/// specify a color (TColor::None), then colors are assigned by the parent (dialog). If the parent
/// has no colors set, default colors will be used.
///
/// If the background color is set to TColor::Transparent, then the background mode of the device
/// context `hDC` is set to TRANSPARENT and NULL_BRUSH is returned. Otherwise a solid brush of the
/// background color is returned.
///
/// \note Parent colors will not override defaults for Edit and List Box controls. For these
/// control types, the colors can only be set by the controls themselves. To set the colors for an
/// Edit or List Box control, encapsulate the control using the corresponding OWL class, and call
/// SetTextColor and SetBkgndColor on the object. For other controls, such as the Static control,
/// the parent will set the control's colors to match the parent's colors, unless explicitly set by
/// the control itself. This ensures that a dialog box is drawn properly. For example, a dialog
/// with labels and fields is drawn with the parent's colors for the labels, but with the system
/// default colors for the fields, unless colors are explicitly set by the controls.
/// Also, setting colors for a Combo Box control has no effect, since this control uses subcontrols
/// for the Edit field and List Box components. Hence, combo boxes need to be treated specially by
/// the parent by handling WM_CTLCOLOREDIT and WM_CTLCOLORLISTBOX, and testing the passed `ctl`
/// window handle for a match against the subcontrols. To determine the handles of the subcontrols,
/// use TComboBox::GetEditHandle and TComboBox::GetListHandle (or TComboBox::GetInfo to get both).
/// These functions are encapsulations of the Windows API function GetComboBoxInfo.
///
/// \code{.cpp}
/// // TComboBox ComboBox;
///
/// DEFINE_RESPONSE_TABLE1(TMyDlg, TDialog)
/// //...
/// EV_WM_CTLCOLOREDIT(EvCtlColor),
/// EV_WM_CTLCOLORLISTBOX(EvCtlColor),
/// END_RESPONSE_TABLE;
///
/// auto TMyDlg::EvCtlColor(HDC hdc, HWND ctl, uint ctlType) -> HBRUSH
/// {
/// const auto i = ComboBox.GetInfo();
/// const auto w = (ctl == i.hwndItem || ctl == i.hwndList) ? ComboBox.GetHandle() : ctl;
/// return TWindow::EvCtlColor(hdc, w, ctlType);
/// }
/// \endcode
///
/// \sa SetBkgndColor, SetTextColor
//
HBRUSH
TWindow::EvCtlColor(HDC hDC, HWND ctl, uint ctlType)
{
// Start by letting the default processing handle the message. This ensures that we get the
// system defaults for the attributes we do not override.
//
const auto defaultErasureBrush = reinterpret_cast<HBRUSH>(DefaultProcessing());
// Now we need to determine which colors to use. If we are handling colors for a child, i.e. the
// ctlType is not CTLCOLOR_DLG, then we let the child specify the color. If the child is not
// encapsulated by OWL, or does not specify a color, then we use our own color. If we do not have
// a color, we use the system default (as provided by the default processing).
//
// Note: We leave the colors of Edit and Listbox controls alone, unless the control is
// encapsulated by an OWL class and explicitly specifies a color (i.e. other than TColor::None).
//
const auto owlChild = (ctlType != CTLCOLOR_DLG) ? GetWindowPtr(ctl) : nullptr;
const auto shouldIgnoreControl = ctlType == CTLCOLOR_EDIT || ctlType == CTLCOLOR_LISTBOX;
const auto textColor = (owlChild && owlChild->GetTextColor() != TColor::None) ? owlChild->GetTextColor() :
shouldIgnoreControl ? TColor::None :
TextColor;
const auto bkgndColor = (owlChild && owlChild->GetBkgndColor() != TColor::None) ? owlChild->GetBkgndColor() :
shouldIgnoreControl ? TColor::None :
BkgndColor;
// Override the control text color, if set.
//
if (textColor != TColor::None)
{
CHECK(textColor != TColor::Transparent);
::SetTextColor(hDC, textColor);
}
// Override the background color, if set, and return a corresponding erasure brush.
//
if (bkgndColor == TColor::None)
{
return defaultErasureBrush;
}
else if (bkgndColor == TColor::Transparent)
{
::SetBkMode(hDC, TRANSPARENT);
return reinterpret_cast<HBRUSH>(::GetStockObject(NULL_BRUSH));
}
else
{
::SetBkColor(hDC, bkgndColor);
return TBrush{bkgndColor}; // HBRUSH will stay in cache.
}
}
//
/// Response method for an incoming WM_PAINT message
///
/// Calls Paint(), performing Windows-required paint setup and cleanup before
/// and after. if the TWindow has a TScroller, also calls its BeginView() and
/// EndView() methods before and after call to Paint()
//
void
TWindow::EvPaint()
{
if (IsFlagSet(wfAlias))
DefaultProcessing(); // use application-defined wndproc
else {
TPaintDC dc(*this);
TRect& rect = *(TRect*)&dc.Ps.rcPaint;
if (Scroller)
Scroller->BeginView(dc, rect);
Paint(dc, dc.Ps.fErase, rect);
if (Scroller)
Scroller->EndView();
}
}
//
/// Repaints the client area (the area you can use for drawing) of a window. Called
/// by base classes when responding to a WM_PAINT message, Paint serves as a
/// placeholder for derived types that define Paint member functions. Paint is
/// called by EvPaint and requested automatically by Windows to redisplay the
/// window's contents. dc is the paint display context supplied to text and graphics
/// output functions. The supplied reference to the rect structure is the bounding
/// rectangle of the area that requires painting. erase indicates whether the
/// background needs erasing.
//
void
TWindow::Paint(TDC&, bool /*erase*/, TRect&)
{
}
//
/// Response method for an incoming WM_SETCURSOR message.
/// If a cursor has been set for this window using TWindow::SetCursor, and the mouse is over the
/// client area, the cursor is used. Otherwise, the cursor for the window class is used (default).
//
bool
TWindow::EvSetCursor(HWND hWndCursor, uint codeHitTest, TMsgId /*mouseMsg*/)
{
if (hWndCursor == GetHandle() && codeHitTest == HTCLIENT && HCursor) {
::SetCursor(HCursor);
return true;
}
return (bool)DefaultProcessing();
}
//
/// Response method for an incoming WM_LBUTTONDOWN message
///
/// If the TWindow's Scroller has been constructed and if auto-scrolling
/// has been requested, captures mouse input, loops until a WM_LBUTTONUP
/// message comes in calling the Scroller's AutoScroll method, and then
/// releases capture on mouse input.
/// Will also break if a WM_MOUSEMOVE comes in without the left button down
/// indicating a lost WM_LBUTTONUP
//
void
TWindow::EvLButtonDown(uint /*modKeys*/, const TPoint& /*pt*/)
{
if (Scroller && Scroller->IsAutoMode()) {
MSG loopMsg;
loopMsg.message = 0;
SetCapture();
while (loopMsg.message != WM_LBUTTONUP && Scroller->IsAutoMode() &&
(loopMsg.message != WM_MOUSEMOVE || (loopMsg.wParam&MK_LBUTTON))) {
if (::PeekMessage(&loopMsg, 0, 0, 0, PM_REMOVE)) {
::TranslateMessage(&loopMsg);
::DispatchMessage(&loopMsg);
// Rethrow any exception suspended in the handler, in which case we need to release
// the mouse capture.
//
try
{
GetApplication()->ResumeThrow();
}
catch (...)
{
ReleaseCapture();
throw;
}
}
Scroller->AutoScroll();
}
ReleaseCapture();
}
DefaultProcessing();
}
//namespace owl {
//
//
//
void
DoEnableAutoCreate(TWindow* win, void* /*retVal*/)
{
if (win->GetHandle())
win->EnableAutoCreate();
}
//} // OWL namespace
//
/// Destroys an MS-Windows element associated with the TWindow.
//
/// First, Destroy calls EnableAutoCreate for each window in the child list to
/// ensure that windows in the child list will be re-created if this is re-created.
/// Then, it destroys the associated interface element.
/// If a derived window class expects to be destructed directly, it should call
/// Destroy as the first step in its destruction so that any virtual functions and
/// event handlers can be called during the destroy sequence.
//
void
TWindow::Destroy(int ret)
{
if (GetHandle())
{
for (auto& w : GetChildren())
{
if (w.GetHandle())
w.EnableAutoCreate();
}
if (IsFlagSet(wfModalWindow)) {
GetApplication()->EndModal(ret);
ClearFlag(wfModalWindow);
if (Parent && Parent->GetHandle())
Parent->SetFocus();
}
if (!IsFlagSet(wfAlias)) {
if (::DestroyWindow(GetHandle()))
SetHandle(0);
}
GetApplication()->ResumeThrow();
WARNX(OwlWin, GetHandle(), 0, _T("::DestroyWindow(") << static_cast<void*>(GetHandle()) << _T(") failed"));
}
}
//
/// Redefined by derived classes, GetWindowClass fills the supplied MS-Windows
/// registration class structure with registration attributes, thus, allowing
/// instances of TWindow to be registered. This function, along with GetWindowClassName,
/// allows Windows classes to be used for the specified ObjectWindows class and its
/// derivatives. It sets the fields of the passed WNDCLASS parameter to the default
/// attributes appropriate for a TWindow. The fields and their default attributes
/// for the class are the following:
/// - \c \b cbClsExtra 0 (the number of extra bytes to reserve after the Window class
/// structure). This value is not used by ObjectWindows.
/// - \c \b cbWndExtra 0 (the number of extra bytes to reserve after the Window instance).
/// This value is not used by ObjectWindows.
/// - \c \b hInstance The instance of the class in which the window procedure exists
/// - \c \b hIcon 0 (Provides a handle to the class resource.) By default, the application
/// must create an icon if the application's window is minimized.
/// - \c \b hCursor IDC_ARROW (provides a handle to a cursor resource)
/// - \c \b hbrBackground COLOR_WINDOW + 1 (the system background color)
/// - \c \b lpszMenuName 0 (Points to a string that contains the name of the class's menu.)
/// By default, the windows in this class have no assigned menus.
/// - \c \b lpszClassName Points to a string that contains the name of the window class.
/// - \c \b lpfnWndProc The address of the window procedure. This value is not used by
/// ObjectWindows.
/// - \c \b style Style field.
///
/// The style field can contain one or more of the following values:
/// - \c \b CS_BYTEALIGNCLIENT Aligns the window's client on a byte boundary in the x
/// direction. This alignment, designed to improve performance, determines the width
/// and horizontal position of the window.
/// - \c \b CS_BYTEALIGNWINDOW Aligns a window on a byte boundary in the x direction. This
/// alignment, designed to improve performance, determines the width and horizontal
/// position of the window.
/// - \c \b CS_CLASSDC Allocates a single device context (DC) that's going to be shared by
/// all of the window in the class. This style controls how multi-threaded
/// applications that have windows belonging to the same class share the same DC.
/// - \c \b CS_DBLCLKS Sends a double-click mouse message to the window procedure when the
/// mouse is double-clicked on a window belonging to this class.
/// - \c \b CS_GLOBALCLASS Allows an application to create a window class regardless of the
/// instance parameter. You can also create a global class by writing a DLL that
/// contains the window class.
/// - \c \b CS_HREDRAW If the size of the window changes as a result of some movement or
/// resizing, redraws the entire window.
/// - \c \b CS_NOCLOSE Disables the Close option on this window's system menu.
/// - \c \b CS_OWNDC Enables each window in the class to have a different DC.
/// - \c \b CS_PARENTDC Passes the parent window's DC to the child windows.
/// - \c \b CS_SAVEBITS Saves the section of the screen as a bitmap if the screen is covered
/// by another window. This bitmap is later used to recreate the window when it is
/// no longer obscured by another window.
/// - \c \b CS_VREDRAW If the height of the client area is changed, redraws the entire
/// window.
///
/// After the Windows class structure has been filled with default values by the
/// base class, you can override this function to change the values of the Windows
/// class structure. For example, you might want to change the window's colors or
/// the cursor displayed.
///
/// Register unique classes for windows that want system background colors so
/// that full-drag erasing uses the right color (NT fails to let window erase
/// itself)
///
void
TWindow::GetWindowClass(WNDCLASS& wndClass)
{
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = *GetModule();
wndClass.hIcon = 0;
wndClass.hCursor = ::LoadCursor(0, IDC_ARROW);
if (BkgndColor.IsSysColor() &&
BkgndColor != TColor::None && BkgndColor != TColor::Transparent)
wndClass.hbrBackground = reinterpret_cast<HBRUSH>(static_cast<INT_PTR>(BkgndColor.Index() + 1));
else
wndClass.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
wndClass.lpszMenuName = 0;
wndClass.lpszClassName = GetWindowClassName().GetPointerRepresentation();
wndClass.style = CS_DBLCLKS;
wndClass.lpfnWndProc = InitWndProc;
}
//
// Return the classname for a generic owl window. Assume instance private class
// registration so that no instance mangling is needed.
//
// Register unique classnames for windows that want system background colors
//
auto TWindow::GetWindowClassName() -> TWindowClassName{
static const tchar baseClassName[] = _T("OWL_Window");
if (BkgndColor.IsSysColor() &&
BkgndColor != TColor::None && BkgndColor != TColor::Transparent) {
static tchar namebuff[COUNTOF(baseClassName) + 1 + 10 + 1 + 4]; // ?? How was this formula calculated
_stprintf(namebuff, _T("%s:%X"), baseClassName, BkgndColor.Index());
// !CQ could hash in classStyle too.
return TWindowClassName{namebuff};
}
return TWindowClassName{baseClassName};
}
//
// Set this window's accelerator table, performing the load also if this window
// is already created
//
void
TWindow::SetAcceleratorTable(TResId resId)
{
Attr.AccelTable = resId;
if (GetHandle())
LoadAcceleratorTable();
else
HAccel = 0;
}
//
/// Loads a handle to the window's accelerator table specified in the TWindowAttr
/// structure (Attr.AccelTable). If the accelerator does not exist,
/// LoadAcceleratorTable produces an "Unable to load accelerator table" diagnostic
/// message.
//
void
TWindow::LoadAcceleratorTable()
{
if (Attr.AccelTable) {
HAccel = LoadAccelerators(Attr.AccelTable);
WARNX(OwlWin, !HAccel, 0,
_T("Unable to load accelerators ") << Attr.AccelTable
<< _T(" from ") << *GetModule());
}
}
//
// Helper used by Create and PerformCreate to make a menu-or-id union parameter
// for CreateWindowEx; see Windows SDK documentation.
//
HMENU
TWindow::MakeMenuOrId()
{
PRECONDITION(GetModule());
HMENU m = Attr.Menu ? LoadMenu(Attr.Menu) : reinterpret_cast<HMENU>(static_cast<INT_PTR>(Attr.Id));
WARNX(OwlWin, Attr.Menu && !m, 0,
_T("Unable to load menu: ") << Attr.Menu << _T(" from ") << *GetModule());
return m;
}
//
/// Called from Create to perform the final step in creating an Windows interface
/// element to be associated with a TWindow. PerformCreate can be overridden to
/// provide alternate implementations.
///
/// In strict mode we ignore the argument passed, and we build the menu-or-id
/// parameter to CreateWindowEx purely based on TWindow data members. In old
/// mode we treat the argument like before and assume that it is already a
/// composed menu-or-id union. In case it represents a menu, we take ownership
/// of the allocated menu (which should have been created by the caller).
//
TWindow::THandle
TWindow::PerformCreate()
{
PRECONDITION(GetModule());
//
// Use RAII to ensure the menu is released in case of failure.
//
struct TMenuGuard
{
HMENU m;
bool is_mine;
TMenuGuard(HMENU m_, bool is_mine_) : m(m_), is_mine(is_mine_) {}
~TMenuGuard() {if (is_mine) DestroyMenu(m);}
operator HMENU() {return m;}
HMENU RelinquishOwnership() {is_mine = false; return m;}
}
menuOrId(MakeMenuOrId(), Attr.Menu != 0);
THandle h = CreateWindowEx(
Attr.ExStyle,
GetWindowClassName().GetPointerRepresentation(),
Title,
Attr.Style,
Attr.X, Attr.Y, Attr.W, Attr.H,
Parent ? Parent->GetHandle() : 0,
menuOrId,
*GetModule(),
Attr.Param
);
if (h) menuOrId.RelinquishOwnership(); // The menu belongs to the window now.
return h;
}
//
/// Creates the window interface element to be associated with this ObjectWindows
/// interface element. Specifically, Create performs the following window creation
/// tasks:
/// - 1. If the HWND already exists, Create returns true. (It is perfectly valid to
/// call Create even if the window currently exists.)
/// - 2. If the wfFromResource flag is set, then Create grabs the HWND based on the
/// window ID. Otherwise, Create registers the window, sets up the window thunk,
/// loads accelerators and menus, and calls PerformCreate in the derived class to
/// create the HWND.
/// - 3. If class registration fails for the window, Create calls TXWindow with
/// IDS_CLASSREGISTERFAIL. If the window creation fails, Create calls TXWindow with
/// IDS_WINDOWCREATEFAIL.
/// - 4. If the window is created for a predefined Window class (for example, a
/// button or dialog class) registered outside of ObjectWindows, then ObjectWindows
/// thunks the window so that it can intercept messages and obtains the state of the
/// window (the window's attributes) from the HWND.
///
/// \note Since this member function now throws an exception on error, it always
/// returns true.
//
bool
TWindow::Create()
{
if (GetHandle())
return true;
DisableAutoCreate();
// If this is the main window, make sure it is treated as one by the shell.
//
if (IsFlagSet(wfMainWindow))
ModifyExStyle(0, WS_EX_APPWINDOW);
if (IsFlagSet(wfFromResource))
SetHandle(Parent ? Parent->GetDlgItem(Attr.Id) : 0); // Windows already created it.
else
{
// Make sure the window class is registered.
//
if (!Register())
TXWindow::Raise(this, IDS_CLASSREGISTERFAIL);
// Perform necessary steps to create the window and attach the window procedure.
//
SetCreationWindow(this);
LoadAcceleratorTable();
CHECK(!GetHandle()); // Ensure handle is NULL in case of exceptions.
// In the new API, PerformCreate takes no arguments.
// Also PerformCreate now returns the handle instead of calling SetHandle.
//
SetHandle(PerformCreate());
GetApplication()->ResumeThrow();
}
WARNX(OwlWin, !GetHandle(), 0, _T("TWindow::Create failed: ") << *this);
if (!GetHandle())
TXWindow::Raise(this, IDS_WINDOWCREATEFAIL);
// Here we deal with non-subclassed handles. This may be caused by two scenarios:
//
// 1. Predefined window class (non-owl) windows.
// 2. OWL controls which were created from resource [Although these
// are registered with InitWndProc, they did not get subclassed since
// 'CreationWindow' was not set when they received their first messages].
//
if (!GetWindowPtr(GetHandle()))
{
// If we have a title, then overwrite the current window text, if any.
// Otherwise sync the title with the current window text.
//
if (Title)
SetCaption(Title);
else
GetWindowTextTitle();
// Grab the state info.
//
GetHWndState();
// If it's a 'predefinedClass' window, subclass it.
//
if (GetWindowProc() != InitWndProc)
{
SubclassWindowFunction();
// Reset the 'CreationWindow' pointer [if necessary].
//
if (GetCreationWindow() == this)
SetCreationWindow(0);
// Set status flag [since we missed EvCreate].
//
SetFlag(wfPredefinedClass);
}
else
{
// This window's WNDPROC is 'InitWndProc' - However, it has not
// been subclassed since 'CreationWindow' was not set when it received
// its first messages [it was probably created from a resource but
// registered by OWL]. We'll set 'CreationWindow' and send a dummy
// message to allow subclassing to take place.
//
if (!GetCreationWindow())
SetCreationWindow(this);
SendMessage(WM_USER+0x4000, 0, 0);
}
// Perform setup and transfer now, since 'EvCreate' was missed earlier.
//
PerformSetupAndTransfer();
}
return true;
}
//
/// Creates the underlying HWND and makes it modal with the help of TApplication's
/// BeginModal support.
//
int
TWindow::Execute()
{
return DoExecute();
}
//
/// Do actual modal execution using the Begin/End Modal support of TApplication.
/// \note Defaults to 'TASKMODAL'.
//
int
TWindow::DoExecute()
{
// Attempting to go modal when one's a child is indicative of
// suicidal tendencies!!
//
PRECONDITION((GetStyle() & WS_CHILD) == 0);
if (GetApplication()) {
if (Create()) {
SetFlag(wfModalWindow);
return GetApplication()->BeginModal(this, MB_TASKMODAL);
}
}
return -1;
}
//
/// Ensures that the window is fully set up; then transfers data into the window.
//
void
TWindow::PerformSetupAndTransfer()
{
SetupWindow();
SetFlag(wfFullyCreated);
// Note that transfer has already happened in SetupWindow if the library is
// built in backwards compatibility mode. See SetupWindow for details.
//
#if !defined(OWL5_COMPAT)
TransferData(tdSetData);
#endif
}
//
/// Performs setup following creation of an associated MS-Windows window.
///
/// The first virtual function called when the HWindow becomes valid. TWindow's
/// implementation performs window setup by iterating through the child list,
/// attempting to create an associated interface element for each child window
/// object for which autocreation is enabled. (By default, autocreation is enabled
/// for windows and controls, and disabled for dialog boxes.)
/// If a child window cannot be created, SetupWindow calls TXWindow
/// with an IDS_CHILDCREATEFAIL message.
///
/// If the receiver has a TScroller object, calls the scroller's SetBarRange()
/// method.
///
/// SetupWindow can be redefined in derived classes to perform additional special
/// initialization. Note that the HWindow is valid when the overridden SetupWindow
/// is called, and that the children's HWindows are valid after calling the base
/// classes' SetupWindow function.
///
/// The following example from the sample program, APPWIN.CPP, illustrates the use
/// of an overridden SetupWindow to setup a window, initialize .INI entries, and
/// tell Windows that we want to accept drag and drop transactions:
/// \code
/// void TAppWindow::SetupWindow()
/// {
/// TFloatingFrame::SetupWindow();
/// InitEntries(); // Initialize .INI entries.
/// RestoreFromINIFile(); // from APPLAUNC.INI in the startup directory
/// UpdateAppButtons();
/// DragAcceptFiles(true);
/// }
/// \endcode
//
void
TWindow::SetupWindow()
{
TRACEX(OwlWin, 1, _T("TWindow::SetupWindow() @") << (void*)this << *this);
// Update scrollbar - if a scroller was attached to the window
//
if (Scroller){
Scroller->SetWindow(this);
Scroller->SetSBarRange();
}
// If this is main Window and GetAplication()->GetTooltip() != 0; create it.
if (IsFlagSet(wfMainWindow)){
TTooltip* tooltip = GetApplication()->GetTooltip();
if(tooltip){
if(!tooltip->GetParentO())
tooltip->SetParent(this);
if(!tooltip->GetHandle()){
// Make sure tooltip is disabled so it does not take input focus
tooltip->ModifyStyle(0,WS_DISABLED);
tooltip->Create();
}
}
}
// NOTE: CreateChildren will throw a TXWindow exception if one of the
// child objects could not be created.
//
CreateChildren();
// Transfer data here for legacy compatibility.
// Note that this may be before any setup in a derived class has completed,
// if that class overrides SetupWindow by calling this base version first,
// as is the usual idiom. This problem has now been fixed, and TransferData
// is now normally called from PerformSetupAndTransfer, thus ensuring that setup
// has been fully completed before transfer. But we retain the old behaviour
// here for compatibility modes to support legacy applications.
//
#if defined(OWL5_COMPAT)
TransferData(tdSetData);
#endif
}
//
/// Always called immediately before the HWindow becomes invalid, CleanupWindow
/// gives derived classes an opportunity to clean up HWND related resources. This
/// function is the complement to SetupWindow.
///
/// Override this function in your derived class to handle window cleanup. Derived
/// classes should call the base class's version of CleanupWindow as the last step
/// before returning. The following example from the sample program, APPWIN.CPP,
/// illustrates this process:
/// \code
/// //Tell windows that we are not accepting drag and drop transactions any more and
/// //perform other window cleanup.
/// void
/// TAppWindow::CleanupWindow()
/// {
/// AppLauncherCleanup();
/// DragAcceptFiles(false);
/// TWindow::CleanupWindow();
/// }
/// \endcode
//
void
TWindow::CleanupWindow()
{
TRACEX(OwlWin, 1, _T("TWindow::CleanupWindow() @") << (void*)this << *this);
}
//
/// Transfers data to or from any window with or without children and returns the
/// total size of the data transferred. Transfer is a general mechanism for data
/// transfer that can be used with or without using TransferData. The direction
/// supplied specifies whether data is to be read from or written to the supplied
/// buffer, or whether the size of the transfer data is simply to be returned. Data
/// is not transferred to or from any child windows whose wfTransfer flag is not
/// set. The return value is the size (in bytes) of the transfer data.
//
uint
TWindow::Transfer(void* buffer, TTransferDirection direction)
{
if (!buffer && direction != tdSizeData) return 0;
//
// Transfer window 'data' to/from the passed data buffer. Used to initialize
// windows and get data in or out of them.
//
// The direction passed specifies whether data is to be read from or written
// to the passed buffer, or whether the data element size is simply to be
// returned
//
// The return value is the size (in bytes) of the transfer data. this method
// recursively calls transfer on all its children that have wfTransfer set.
//
auto p = buffer;
for (auto& w : GetChildren())
{
if (w.IsFlagSet(wfTransfer))
p = (char*) p + w.Transfer(p, direction);
}
return static_cast<uint>(reinterpret_cast<char*>(p) - reinterpret_cast<char*>(buffer));
}
//
/// Transfers data between the TWindow's data buffer and the child
/// windows in its ChildList (data is not transfered between any child
/// windows whose wfTransfer flag is not set)
///
/// A window usually calls TransferData during setup and closing of windows and
/// relies on the constructor to set TransferBuffer to something meaningful.
/// TransferData calls the Transfer member function of each participating child
/// window, passing a pointer to TransferBuffer as well as the direction specified
/// in direction (tdSetData, tdGetData, or tdSizeData).
//
void
TWindow::TransferData(TTransferDirection direction)
{
if (!TransferBuffer) return; // nothing to do
uint size = Transfer(TransferBuffer, tdSizeData);
if (direction == tdSizeData) return; // done
if (TransferBufferSize > 0 && size != TransferBufferSize)
TXWindow::Raise(this, IDS_TRANSFERBUFFERSIZEMISMATCH);
WARN(TransferBufferSize == 0,
_T("TWindow::TransferData: Unsafe transfer is performed! ")
_T("Use one of the safe overloads of SetTransferBuffer to enable buffer checks."));
Transfer(TransferBuffer, direction);
}
//
// Checks whether the given HWND belongs to this process.
// Internal function.
//
inline static bool BelongsToCurrentProcess (HWND h)
{
DWORD processId = 0;
::GetWindowThreadProcessId(h, &processId);
return processId == ::GetCurrentProcessId();
}
//
/// Installs the instance thunk as the WindowProc and saves the old window function
/// in DefaultProc.
//
void
TWindow::SubclassWindowFunction()
{
PRECONDITION(GetHandle());
PRECONDITION(GetInstanceProc());
if (!BelongsToCurrentProcess(GetHandle())) return;
// If the window already has the window proc we want,
// then just ensure that it has a default proc and return.
//
if (GetInstanceProc() == GetWindowProc())
{
if (!DefaultProc) DefaultProc = ::DefWindowProc;
return;
}
// Initialize the instance proc and install it.
//
InitInstanceProc();
DefaultProc = SetWindowProc(GetInstanceProc());
}
//
/// Registers the Windows registration class of this window, if this window is not
/// already registered. Calls GetWindowClassName and GetWindowClass to retrieve the
/// Windows registration class name and attributes of this window. Register returns
/// true if this window is registered.
//
bool
TWindow::Register()
{
auto& m = *GetModule();
const auto c = GetWindowClassName();
if (m.IsRegisteredClass(c)) return true;
auto w = WNDCLASS{};
GetWindowClass(w);
CHECK(c.GetPointerRepresentation() == w.lpszClassName);
return m.IsRegisteredClass(c) ? true : ::RegisterClass(&w);
}
//
/// Use this function to determine if it is okay to close a window. Returns true if
/// the associated interface element can be closed. Calls the CanClose member
/// function of each of its child windows. Returns false if any of the CanClose
/// calls returns false.
/// In your application's main window, you can override TWindow's CanClose and call
/// TWindow::MessageBox to display a YESNOCANCEL message prompting the user as
/// follows:
/// - \c \b YES Save the data
/// - \c \b NO Do not save the data, but close the window
/// - \c \b CANCEL Cancel the close operation and return to the edit window
///
/// The following example shows how to write a CanClose function that displays a
/// message box asking if the user wants to save a drawing that has changed. To save
/// time, CanClose uses the IsDirty flag to see if the drawing has changed. If so,
/// CanClose queries the user before closing the window.
/// \code
/// bool TMyWindow::CanClose()
/// {
/// if (IsDirty)
/// switch(MessageBox("Do you want to save?", "Drawing has changed.",
/// MB_YESNOCANCEL | MB_ICONQUESTION)) {
/// case IDCANCEL:
/// // Choosing Cancel means to abort the close -- return false.
/// return false;
///
/// case IDYES:
/// // Choosing Yes means to save the drawing.
/// CmFileSave();
/// }
/// return true;
/// }
/// \endcode
//
bool
TWindow::CanClose()
{
auto c = GetChildren();
return all_of(c.begin(), c.end(), [](TWindow& w) { return !w.GetHandle() || w.CanClose(); });
}
//
/// Determines if it is okay to close a window before actually closing the window.
/// If this is the main window of the application, calls GetApplication->CanClose.
/// Otherwise, calls this->CanClose to determine whether the window can be closed.
/// After determining that it is okay to close the window, CloseWindow calls Destroy
/// to destroy the HWND.
//
void
TWindow::CloseWindow(int retVal)
{
bool willClose;
if (IsFlagSet(wfMainWindow))
willClose = GetApplication()->CanClose();
else
willClose = CanClose();
if (willClose)
Destroy(retVal);
}
//
/// The default response to a WM_CLOSE message is to call CloseWindow()
/// and then have the window deleted if the Handle was really destroyed.
//
void
TWindow::EvClose()
{
if (IsFlagSet(wfAlias))
DefaultProcessing();
else {
CloseWindow();
if (!GetHandle() && IsFlagSet(wfDeleteOnClose))
GetApplication()->Condemn(this); // Assumes delete
}
}
//
/// Responds to an incoming WM_DESTROY message
///
/// Calls CleanupWindow() to let derived classes cleanup
/// Clears the wfFullyCreated flag since this window is no longer fully created
///
/// If the TWindow is the application's main window posts a 'quit' message to
/// end the application, unless already in ~TApplication() (MainWindow == 0)
//
void
TWindow::EvDestroy()
{
ClearFlag(wfFullyCreated);
CleanupWindow();
if (!IsFlagSet(wfAlias)) {
if (IsFlagSet(wfMainWindow) && GetApplication()->IsRunning())
::PostQuitMessage(0);
}
DefaultProcessing();
}
//
/// Responds to an incoming WM_NCDESTROY message, the last message
/// sent to an MS-Windows interface element
///
/// Sets the Handle data member of the TWindow to zero to indicate that an
/// interface element is no longer associated with the object
//
void
TWindow::EvNCDestroy()
{
DefaultProcessing();
SetHandle(0);
}
//
/// Respond to Windows attempt to close down. Determines if this app or window
/// is ready to close.
//
bool
TWindow::EvQueryEndSession(uint /*flags*/)
{
if (IsFlagSet(wfAlias))
return (bool)DefaultProcessing();
else if (IsFlagSet(wfMainWindow))
return GetApplication()->CanClose();
else
return CanClose();
}
//
/// Provides default handling for WM_ENDSESSION.
/// If the session is ending, throws the exception TXEndSession, thus shutting down the
/// entire applicaton.
//
void
TWindow::EvEndSession(bool endSession, uint /*flags*/)
{
if (endSession)
throw TXEndSession();
else
DefaultProcessing();
}
//
/// Handle message posted to us by a control needing assistance in dealing with
/// invalid inputs
///
/// Responds to a WM_CHILDINVALID message posted by a child edit control. Indicates
/// that the contents of the child window are invalid.
//
void
TWindow::EvChildInvalid(HWND handle)
{
PRECONDITION(handle);
::SendMessage(handle, WM_CHILDINVALID, 0, 0);
}
//----------------------------------------------------------------------------
// Non-virtuals
//
void
TWindow::AttachHandle(HWND handle)
{
ClearFlag(wfDetached);
FreeInstanceProc();
InstanceProc = 0;
Init(handle, GetModule());
}
void
TWindow::DetachHandle()
{
// NOTE: This is by no means a complete way of detaching the window...
// The following is logic allows Delphi/OWL interaction..
//
ClearFlag(wfFullyCreated);
SetHandle(0);
SetFlag(wfDetached);
}
//
/// Returns the number of child windows of the window.
//
unsigned
TWindow::NumChildren() const
{
return IndexOf(ChildList) + 1;
}
//
// Walks over children and assigns a z-order index to the ZOrder member
//
void
TWindow::AssignZOrder()
{
TWindow* wnd;
HWND curWindow = GetHandle();
if (curWindow) {
curWindow = ::GetWindow(curWindow, GW_CHILD);
if (curWindow) {
int i = 1;
for (curWindow = ::GetWindow(curWindow, GW_HWNDLAST);
curWindow;
curWindow = ::GetWindow(curWindow, GW_HWNDPREV))
{
wnd = GetWindowPtr(curWindow);
if (wnd)
wnd->ZOrder = (uint16)i++;
}
}
}
}
//
/// Creates the child windows in the child list whose auto-create flags (with
/// wfAutoCreate mask) are set. If all of the child windows are created
/// successfully, CreateChildren returns true.
/// \note Throws an exception (TXWindow) if a child object could not be
/// created.
//
bool
TWindow::CreateChildren()
{
// Create children first to restore creation order.
//
for (auto& child : GetChildren())
{
//
if (child.GetHandle())
continue;
bool autoCreate = child.IsFlagSet(wfAutoCreate);
WARNX(OwlWin, !autoCreate, 0, _T("Child window(Id=") << child.GetId() << _T(") not autocreated"));
if (!autoCreate)
continue;
// This call will only fail if a user-defined Create() returns false.
// OwlNext's Create-implementations always throw exceptions.
//
if (!child.Create())
throw TXWindow{&child, IDS_WINDOWCREATEFAIL};
// Get & set the window text for the child if it is iconic.
// TODO: Review if this hack is still necessary.
//
if (child.IsIconic())
child.SetWindowText(child.GetWindowText());
}
// Restore Z-ordering for children that have Z-ordering recorded.
//
HWND above = HWND_TOP;
for (int top = NumChildren(); top; top--)
{
auto c = GetChildren();
const auto i = find_if(c.begin(), c.end(), [&top](TWindow& w) { return w.ZOrder == top; });
if (i != c.end())
{
TWindow& child = *i;
child.SetWindowPos(above, TRect{}, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
above = child.GetHandle();
}
}
return true;
}
//
// adds the passed pointer to a child window to the linked list
// of sibling windows which ChildList points to
//
void
TWindow::AddChild(TWindow* child)
{
if (!child) return;
if (ChildList)
{
child->SiblingList = ChildList->SiblingList;
ChildList->SiblingList = child;
ChildList = child;
}
else
{
ChildList = child;
child->SiblingList = child;
}
}
//
/// Returns a pointer to the TWindow's previous sibling (the window previous to
/// the TWindow in its parent's child window list)
///
/// If the TWindow was the first child added to the list, returns a pointer to
/// the last child added
//
TWindow*
TWindow::Previous()
{
if (!SiblingList) {
return 0;
}
else {
TWindow* CurrentIndex = this;
while (CurrentIndex->Next() != this)
CurrentIndex = CurrentIndex->Next();
return CurrentIndex;
}
}
#if defined(OWL5_COMPAT)
//
/// Returns a pointer to the first TWindow in the ChildList that meets some
/// specified criteria
///
/// If no child in the list meets the criteria, 0 is returned
///
/// The Test parameter which defines the criteria, is a pointer to a
/// function that takes a TWindow pointer & a pointer to void
///
/// The TWindow* will be used as the pointer to the child window and
/// the void* as a pointer to the Test function's additional parameters
///
/// The Test function must return a Boolean value indicating whether the
/// child passed meets the criteria
///
/// In the following example, GetFirstChecked calls FirstThat to obtain a pointer
/// (p) to the first check box in the child list that is checked:
/// \code
/// bool IsThisBoxChecked(TWindow* p, void*) {
/// return ((TCheckBox*)p)->GetCheck() == BF_CHECKED;
/// }
/// TCheckBox* TMyWindow::GetFirstChecked() {
/// return FirstThat(IsThisBoxChecked);
/// }
/// \endcode
//
TWindow*
TWindow::FirstThat(TCondFunc test, void* paramList) const
{
if (ChildList) {
TWindow* nextChild = ChildList->Next();
TWindow* curChild;
do {
curChild = nextChild;
nextChild = nextChild->Next();
//
// Test curChild for okay
//
if (test(curChild, paramList))
return curChild;
} while (curChild != ChildList && ChildList);
}
return 0;
}
//
/// Iterates over each child window in the TWindow's ChildList,
/// calling the procedure whose pointer is passed as the Action to be
/// performed for each child
///
/// A pointer to a child is passed as the first parameter to the iteration
/// procedure
///
/// In the following example, CheckAllBoxes calls ForEach, checking all the check
/// boxes in the child list:
/// \code
/// void CheckTheBox(TWindow* p, void*) {
/// ((TCheckBox*)p)->Check();
/// }
/// void CheckAllBoxes() {
/// ForEach(CheckTheBox);
/// }
/// \endcode
//
void
TWindow::ForEach(TActionFunc action, void* paramList)
{
if (ChildList) {
TWindow* curChild;
TWindow* nextChild = ChildList->Next(); // Allows ForEach to delete children
do {
curChild = nextChild;
nextChild = nextChild->Next();
action(curChild, paramList);
} while (curChild != ChildList && ChildList);
}
}
//
/// Returns a pointer to the first TWindow in the ChildList that
/// meets some specified criteria
///
/// If no child in the list meets the criteria, 0 is returned
///
/// The Test parameter which defines the criteria, is a pointer to a member
/// function (this is how it's different from FirstThat above) that takes a
/// pointer to a TWindow & a pointer to void
///
/// The TWindow pointer will be used as the pointer to the child window and the
/// void pointer as a pointer to the Test function's additional parameters
///
/// The Test function must return a Boolean value indicating whether the child
/// passed meets the criteria
TWindow*
TWindow::FirstThat(TCondMemFunc test, void* paramList)
{
if (ChildList) {
TWindow* nextChild = ChildList->Next();
TWindow* curChild;
do {
CHECKX(nextChild, _T("The child list was corrupted/modified while traversing it"));
curChild = nextChild;
nextChild = nextChild->Next();
if ((this->*test)(curChild, paramList))
return curChild;
} while (curChild != ChildList && ChildList);
}
return 0;
}
//
/// Iterates over each child window in the TWindow's ChildList,
/// calling the member function (unlike ForEach above which takes pointer
/// to non-member function) whose pointer is passed as the Action to
/// be performed for each child
///
/// A pointer to a child is passed as the first parameter to the iteration
/// procedure
//
void
TWindow::ForEach(TActionMemFunc action, void* paramList)
{
if (ChildList) {
TWindow* nextChild = ChildList->Next();
TWindow* curChild;
do {
CHECKX(nextChild, _T("The child list was corrupted/modified while traversing it"));
curChild = nextChild;
nextChild = nextChild->Next();
(this->*action)(curChild, paramList);
} while (curChild != ChildList && ChildList);
}
}
#endif
//
/// Returns the position at which the passed child window appears in the TWindow's ChildList.
/// Position 0 references the first child, as returned by TWindow::GetFirstChild.
///
/// \returns -1 is returned, if the child does not appear in the list.
//
int TWindow::IndexOf(const TWindow* child) const
{
auto i = 0;
for (auto& c : GetChildren())
if (&c == child)
return i;
else
++i;
return -1;
}
//
/// Returns the child at the passed position in the TWindow's ChildList
/// Position 0 references the first child, as returned by TWindow::GetFirstChild.
///
/// \returns `nullptr` is returned, if the position is negative.
///
/// \note Passing a position larger or equal to the number of children, i.e. `At(i)` for *i* larger
/// or equal to *n*, where *n* equals the value returned by TWindow::NumChildren, will be
/// equivalent to `At(i % n)`. This behaviour is for backwards compatibility reasons, for legacy
/// code that depends on the original implementation, which did circular traversal. However, the
/// list is never traversed circularly in the current implementation. Instead, `i % n` is
/// calculated as the number of links to traverse.
//
TWindow* TWindow::At(int position)
{
if (position < 0) return nullptr;
position %= NumChildren();
auto i = 0;
for (auto& w : GetChildren())
if (i == position)
return &w;
else
++i;
return nullptr; // Should never get here, though.
}
//
/// Returns a pointer to the window in the child window list that has the supplied
/// id. Returns 0 if no child window has the indicated id.
//
auto TWindow::ChildWithId(int id) -> TWindow*
{
auto c = GetChildren();
const auto i = find_if(c.begin(), c.end(), [id](TWindow& w) { return w.GetId() == id; });
return (i != c.end()) ? &(*i) : nullptr;
}
//
/// Sends a message (msg) to a specified window or windows. After it calls the
/// window procedure, it waits until the window procedure has processed the message
/// before returning.
//
TResult
TWindow::SendMessage(TMsgId msg, TParam1 param1, TParam2 param2) const
{
PRECONDITION(GetHandle());
TResult result = ::SendMessage(GetHandle(), msg, param1, param2);
GetApplication()->ResumeThrow();
return result;
}
//
/// Forwards the window's current message. Calls SendMessage if send is true;
/// otherwise calls PostMessage.
//
TResult
TWindow::ForwardMessage(HWND handle, bool send)
{
if (!handle)
return 0;
TCurrentEvent& currentEvent = GetCurrentEvent();
if (send) {
TResult result = ::SendMessage(handle, currentEvent.Message,
currentEvent.Param1,
currentEvent.Param2);
GetApplication()->ResumeThrow();
return result;
}
else
return ::PostMessage(handle, currentEvent.Message,
currentEvent.Param1,
currentEvent.Param2);
}
//
/// Forwards the window's current message. Calls SendMessage if send is true;
/// otherwise calls PostMessage.
//
TResult
TWindow::ForwardMessage(bool send)
{
TCurrentEvent& currentEvent = GetCurrentEvent();
if (send)
return HandleMessage(currentEvent.Message, currentEvent.Param1,
currentEvent.Param2);
return ForwardMessage(GetHandle(), send);
}
//
/// Sends the specified message to all immediate children using SendMessage.
/// \note Includes non-object windows
//
void
TWindow::ChildBroadcastMessage(TMsgId msg, TParam1 param1, TParam2 param2)
{
for (HWND hWndChild = GetWindow(GW_CHILD); hWndChild; ) {
HWND hWndNext = ::GetWindow(hWndChild, GW_HWNDNEXT);
::SendMessage(hWndChild, msg, param1, param2);
GetApplication()->ResumeThrow();
hWndChild = hWndNext;
}
}
//
/// Posts messages when the mouse pointer leaves a window or hovers over a
/// window for a specified amount of time.
/// Encapsulates the eponymous Windows API function.
/// http://msdn.microsoft.com/en-gb/library/windows/desktop/ms646265.aspx
//
bool
TWindow::TrackMouseEvent(uint flags, int hoverTime)
{
TRACKMOUSEEVENT a = {sizeof(TRACKMOUSEEVENT), flags, GetHandle(), static_cast<DWORD>(hoverTime)};
return ::TrackMouseEvent(&a) != FALSE;
}
//
/// Encapsulates a call to TrackMouseEvent, passing the TME_CANCEL flag.
/// See TrackMouseEvent.
//
bool
TWindow::CancelMouseEvent(uint flags)
{
return TrackMouseEvent(flags | TME_CANCEL);
}
//
/// Returns the current state of mouse event tracking initiated by TrackMouseEvent.
/// Encapsulates a call to ::&TrackMouseEvent, passing the TME_QUERY flag.
/// See TrackMouseEvent and Windows API structure TRACKMOUSEEVENT.
/// http://msdn.microsoft.com/en-gb/library/windows/desktop/ms645604.aspx
//
TRACKMOUSEEVENT
TWindow::QueryMouseEventTracking() const
{
TRACKMOUSEEVENT a = {sizeof(TRACKMOUSEEVENT), TME_QUERY, GetHandle()};
BOOL r = ::TrackMouseEvent(&a);
CHECK(r); InUse(r);
return a;
}
//
/// This version of ShutDownWindow unconditionally shuts down a given window, calls
/// Destroy on the interface element, and then deletes the interface object. Instead
/// of using ShutDownWindow, you can call Destroy directly and then delete the
/// interface object.
///
/// \note This function is static to avoid side effects of deleting 'this'.
//
void
TWindow::ShutDownWindow(TWindow* win, int retVal)
{
win->Destroy(retVal);
delete win;
}
//
/// Copies title to an allocated string pointed to by title. Sets the caption of the
/// interface element to title. Deletes any previous title.
/// If the given title is 0, then the internal caption is initialized to 0, and no update
/// of the interface element is done.
//
void
TWindow::SetCaption(LPCTSTR title)
{
if (Title != title) {
if (Title)
delete[] Title;
Title = title ? strnewdup(title) : 0;
}
if (Title && GetHandle())
::SetWindowText(GetHandle(), Title);
}
//
/// Sets the window title to the resource string identified by the given id.
//
void
TWindow::SetCaption(uint resourceStringId)
{
SetCaption(LoadString(resourceStringId));
}
//
/// Updates the TWindow internal caption (Title) from the current window's caption.
/// GetWindowTextTitle is used to keep Title synchronized with the actual window
/// state when there is a possibility that the state might have changed.
//
void
TWindow::GetWindowTextTitle()
{
// NB! Previously (<= 6.30) Title was here checked against 0xXXXXFFFF; a flag for "don't-change".
// See comment in TDialog::Init for more details.
if (Title)
delete[] Title;
int titleLength = GetWindowTextLength();
if (titleLength < 0) {
Title = strnewdup(_T(""));
}
else {
Title = new tchar[titleLength + 1];
Title[0] = 0;
Title[titleLength] = 0;
GetWindowText(Title, titleLength + 1);
}
}
//
/// Copies the style, coordinate, and the resource id (but not the title) from the
/// existing HWnd into the TWindow members.
/// \note The title is not copied here, but in GetWindowTextTitle()
//
void
TWindow::GetHWndState(bool forceStyleSync)
{
// Retrieve Attr.Style and Attr.ExStyle
//
// NOTE: some windows controls (e.g. EDIT) change the style bits
// (e.g. WS_BORDER) from their original values. if we always reset
// Attr.Style and Attr.ExStyle by extracting their values from
// Windows, we will lose some of the style bits we supplied
// in the CreateWindowEx call. in the case of the ResourceId
// constructors, of course, we must retrieve these values.
//
if (IsFlagSet(wfFromResource) || forceStyleSync) {
Attr.Style = GetWindowLong(GWL_STYLE);
Attr.ExStyle = GetWindowLong(GWL_EXSTYLE);
}
// Retrieve Attr.X, Attr.Y, Attr.W and Attr.H
//
TRect wndRect;
GetWindowRect(wndRect);
Attr.H = wndRect.Height();
Attr.W = wndRect.Width();
HWND hParent = GetParentH();
if ((Attr.Style & WS_CHILD) && hParent)
::ScreenToClient(hParent, &wndRect.TopLeft());
Attr.X = wndRect.left;
Attr.Y = wndRect.top;
Attr.Id = GetWindowLong(GWL_ID);
}
//
/// Translates the text of a specified control into an integer value and returns it.
/// The parameter 'translated' points to a variable that is set to 'true' on success, 'false' otherwise.
/// The parameter 'isSigned' indicates that the retrieved value is signed (the default).
/// \note Wraps the corresponding function in the Windows API.
//
uint
TWindow::GetDlgItemInt(int childId, bool* translated, bool isSigned) const
{
PRECONDITION(GetHandle());
BOOL tempTranslated;
uint retVal = ::GetDlgItemInt(GetHandle(), childId, &tempTranslated, isSigned);
if (translated)
*translated = tempTranslated;
return retVal;
}
//
// Implementation functions for style getters and mutators.
//
namespace {
template <int GwlStyle, uint32 TWindowAttr::*StyleMem>
auto GetStyle_(const TWindow* w) -> uint32
{
return w->GetHandle() ? w->GetWindowLong(GwlStyle) : (w->Attr.*StyleMem);
}
template <int GwlStyle, uint32 TWindowAttr::*StyleMem>
auto SetStyle_(TWindow* w, uint32 style) -> uint32
{
const auto setStyleMem = [](TWindow* w, uint32 style)
{
const auto oldstyle = w->Attr.*StyleMem;
w->Attr.*StyleMem = style;
return oldstyle;
};
return w->GetHandle() ? w->SetWindowLong(GwlStyle, style) : setStyleMem(w, style);
}
template <uint32 (TWindow::*GetStyleMemFun)() const, uint32 (TWindow::*SetStyleMemFun)(uint32)>
auto ModifyStyle_(TWindow* w, uint32 offBits, uint32 onBits, uint swpFlags) -> bool
{
const auto style = (w->*GetStyleMemFun)();
const auto newStyle = (style & ~offBits) | onBits;
const auto isDifferent = style != newStyle;
if (isDifferent)
{
(w->*SetStyleMemFun)(newStyle);
if (swpFlags != 0)
w->SetWindowPos(nullptr, {}, swpFlags | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
}
return isDifferent;
}
} // anonymous namespace
//
/// Gets the style bits of the underlying window or the 'Style' member of the
/// attribute structure associated with this TWindow object.
//
auto TWindow::GetStyle() const -> uint32
{
return GetStyle_<GWL_STYLE, &TWindowAttr::Style>(this);
}
//
/// Sets the style bits of the underlying window or the 'Style' member of the
/// attribute structure associated with this TWindow object.
//
auto TWindow::SetStyle(uint32 style) -> uint32
{
return SetStyle_<GWL_STYLE, &TWindowAttr::Style>(this, style);
}
//
/// Gets the extra style bits of the window.
//
auto TWindow::GetExStyle() const -> uint32
{
return GetStyle_<GWL_EXSTYLE, &TWindowAttr::ExStyle>(this);
}
//
/// Sets the extra style bits of the window.
//
auto TWindow::SetExStyle(uint32 style) -> uint32
{
return SetStyle_<GWL_EXSTYLE, &TWindowAttr::ExStyle>(this, style);
}
//
/// Modifies the style bits of the window.
//
auto TWindow::ModifyStyle(uint32 offBits, uint32 onBits, uint swpFlags) -> bool
{
return ModifyStyle_<&TWindow::GetStyle, &TWindow::SetStyle>(this, offBits, onBits, swpFlags);
}
//
/// Modifies the style bits of the window.
//
auto TWindow::ModifyExStyle(uint32 offBits, uint32 onBits, uint swpFlags) -> bool
{
return ModifyStyle_<&TWindow::GetExStyle, &TWindow::SetExStyle>(this, offBits, onBits, swpFlags);
}
//
/// Gets the screen coordinates of the window's rectangle and copies them into rect.
/// \note Gets the window rectangle whether it has been created or not
//
void
TWindow::GetWindowRect(TRect& rect) const {
if (GetHandle()) {
::GetWindowRect(GetHandle(), &rect);
}
else {
rect = {{Attr.X, Attr.Y}, TSize{Attr.W, Attr.H}};
}
}
//
/// Gets the coordinates of the window's client area and then copies them into the
/// object referred to by TRect.
/// \note Gets the window's client rectangle whether it has been created or not
//
void
TWindow::GetClientRect(TRect& rect) const {
if (GetHandle()) {
::GetClientRect(GetHandle(), &rect);
}
else {
rect = {{0, 0}, TSize{Attr.W, Attr.H}};
}
}
//
/// Set the new window position.
//
/// Changes the size of the window pointed to by x, y, w, and h. flags contains one
/// of the SWP_Xxxx Set Window Position constants that specify the size and
/// position of the window. If flags is set to SWP_NOZORDER, SetWindowPos ignores
/// the hWndInsertAfter parameter and retains the current ordering of the child,
/// pop-up, or top-level windows.
///
/// SWP_Xxxx Set Window Position Constants
///
/// - \c \b SWP_DRAWFRAME Draws a frame around the window.
/// - \c \b SWP_FRAMECHANGED Sends a message to the window to recalculate the window's
/// size. If this flag is not set, a recalculate size message is sent only at the
/// time the window's size is being changed.
/// - \c \b SWP_HIDEWINDOW Hides the window.
/// - \c \b SWP_NOACTIVATE Does not activate the window. If this flag is not set, the
/// window is activated and moved to the top of the stack of windows.
/// - \c \b SWP_NOCOPYBITS Discards the entire content area of the client area of the
/// window. If this flag is not set, the valid contents are saved and copied into
/// the window after the window is resized or positioned.
/// - \c \b SWP_NOMOVE Remembers the window's current position.
/// - \c \b SWP_NOSIZE Remembers the window's current size.
/// - \c \b SWP_NOREDRAW Does not redraw any changes to the window. If this flag is set, no
/// repainting of any window area (including client, nonclient, and any window part
/// uncovered as a result of a move) occurs. When this flag is set, the application
/// must explicitly indicate if any area of the window is invalid and needs to be
/// redrawn.
/// - \c \b SWP_NOZORDER Remembers the current Z-order (window stacking order).
/// - \c \b SWP_SHOWWINDOW Displays the window.
//
bool
TWindow::SetWindowPos(HWND hWndInsertAfter,
int x, int y, int w, int h,
uint flags)
{
if (GetHandle())
return ::SetWindowPos(GetHandle(), hWndInsertAfter, x, y, w, h, flags);
if (!(flags & SWP_NOMOVE)) {
Attr.X = x;
Attr.Y = y;
}
if (!(flags & SWP_NOSIZE)) {
Attr.W = w;
Attr.H = h;
}
// !CQ Can't do much with Z-Order or owner Z-Order
if (flags & SWP_SHOWWINDOW)
Attr.Style |= WS_VISIBLE;
else if (flags & SWP_HIDEWINDOW)
Attr.Style &= ~WS_VISIBLE;
return true;
}
//
/// Displays this TWindow in a given state. Can be called either before or after
/// the Window is created. If before, the show state is placed into Attr for use
/// at creation
///
/// After ensuring that the TWindow interface element has a valid handle, ShowWindow
/// displays the TWindow on the screen in a manner specified by cmdShow, which can
/// be one of the following SW_Xxxx Show Window constants:
/// - \c \b SW_SHOWDEFAULT Show the window in its default configuration. Should be used at
/// startup.
/// - \c \b SW_HIDE Hide the window and activate another window.
/// - \c \b SW_MINIMIZE Minimize the window and activate the top-level window in the list.
/// - \c \b SW_RESTORE Same as SW_SHOWNORMAL.
/// - \c \b SW_SHOW Show the window in the window's current size and position.
/// - \c \b SW_SHOWMAXIMIZED Activate and maximize the window.
/// - \c \b SW_SHOWMINIMIZED Activate window as an icon.
/// - \c \b SW_SHOWNA Display the window as it is currently.
/// - \c \b SW_SHOWMINNOACTIVE Display the window as an icon.
/// - \c \b SW_SHOWNORMAL Activate and display the window in its original size and position.
/// - \c \b SW_SHOWSMOOTH Show the window after updating it in a bitmap.
//
bool
TWindow::ShowWindow(int cmdShow)
{
// If the window is being minimzed send a WM_SYSCOMMAND; this way the
// frame window focus saving works properly
// !CQ do we still need this with final owl2 focus saving?
//
if (gBatchMode)
return true;
if (GetHandle()) {
if (cmdShow == SW_MINIMIZE)
return HandleMessage(WM_SYSCOMMAND, SC_MINIMIZE);
else
return ::ShowWindow(GetHandle(), cmdShow);
}
switch (cmdShow) {
case SW_HIDE:
Attr.Style &= ~WS_VISIBLE;
break;
case SW_SHOWNORMAL:
case SW_RESTORE:
Attr.Style |= WS_VISIBLE;
Attr.Style &= ~(WS_MINIMIZE | WS_MAXIMIZE);
break;
case SW_SHOWMINIMIZED:
case SW_MINIMIZE:
case SW_SHOWMINNOACTIVE:
Attr.Style |= WS_VISIBLE;
Attr.Style |= WS_MINIMIZE;
break;
case SW_SHOWMAXIMIZED:
Attr.Style |= WS_VISIBLE;
Attr.Style |= WS_MAXIMIZE;
break;
case SW_SHOWNOACTIVATE:
case SW_SHOW:
case SW_SHOWNA:
Attr.Style |= WS_VISIBLE;
break;
}
return true;
}
//
/// Sets the mouse cursor for the window, loading the given `resId` from the given `module`.
/// If `module` is `nullptr`, then `resId` can be one of the IDC_xxxx constants that represent
/// different kinds of cursors. See the Windows documentation for a list of valid values.
/// If the mouse is over the client area, the function immediately updates the cursor.
/// If `resId` is 0, then the cursor for the window class is used, according to the default
/// processing for the WM_SETCURSOR message (see EvSetCursor).
//
bool
TWindow::SetCursor(TModule* module, TResId resId)
{
if (module == CursorModule && resId == CursorResId)
return false;
HCURSOR hOldCursor = (HCursor && CursorModule) ? HCursor : 0;
CursorModule = module;
CursorResId = resId;
if (CursorResId)
if (CursorModule)
HCursor = CursorModule->LoadCursor(CursorResId);
else
HCursor = ::LoadCursor(0, CursorResId);
else
HCursor = 0;
// If the cursor is in our client window then set it now
//
if (GetHandle()) {
TPoint p;
GetCursorPos(p);
ScreenToClient(p);
if (GetClientRect().Contains(p))
{
auto getClassCursor = [&] { return reinterpret_cast<HCURSOR>(::GetClassLongPtr(GetHandle(), GCLP_HCURSOR)); };
::SetCursor(HCursor ? HCursor : getClassCursor());
}
}
// Destroy old cursor if there was one & it was not loaded from USER
//
if (hOldCursor)
::DestroyCursor(hOldCursor);
return true;
}
//
/// Handle WM_INITMENUPOPUP while embeded to generate command enable messages
/// for our server menu items. Very similar to TFrameWindow::EvInitMenuPopup;
/// could rearrange code to share better.
//
void
TWindow::EvInitMenuPopup(HMENU hPopupMenu, uint /*index*/, bool isSysMenu)
{
if (IsFlagSet(wfAlias))
DefaultProcessing();
else if (!isSysMenu && hPopupMenu) {
const int count = ::GetMenuItemCount(hPopupMenu);
for (int pos = 0; pos < count; pos++) {
uint id;
if (hPopupMenu == GetMenu()) // top level menu
id = ::GetMenuItemID(hPopupMenu, pos);
else {
// For second level and below menus, return the implied id for popup
// sub-menus. The implied id for a sub-menu is the id of the first item
// in the popup sub-menu less 1. If there are more than one level of
// popup menus, it will recursively look into those sub-menus as well.
//
TMenu popupMenu(hPopupMenu);
id = popupMenu.GetMenuItemID(pos);
}
// Ignore separators
//
if (id == 0 || uint16(id) == uint16(-1))
continue;
// Skip the rest if it is the mdi child list, or system commands
//
if (id == (uint)IDW_FIRSTMDICHILD || id > 0xF000)
break;
TMenuItemEnabler mie(hPopupMenu, id, GetHandle(), pos);
EvCommandEnable(mie);
}
}
}
//
/// Sets the font that a control uses to draw text.
/// If the given font owns the underlying font handle (HFONT), the window will share ownership. If
/// the given font does not own the handle, neither will the window. In any case, the given TFont
/// object does not need to outlive the window. However, if the underlying font handle is not
/// owned, the caller must make sure that the font handle outlives the window. In this last case,
/// the call is equivalent to `SetWindowFont (font.GetHandle(), redraw)`.
/// \note Wrapper for Windows API.
//
void
TWindow::SetWindowFont(const TFont& font, bool redraw)
{
WARN(!font.IsHandleOwner(), _T("The given font has (a font handle with) unmanaged lifetime."));
if (!font.IsHandleOwner())
return SetWindowFont(font.GetHandle(), redraw);
// Note: The TFont copy constructor shares the font handle. It does not create a new font.
//
Font = make_unique<TFont>(font);
SetWindowFont(Font->GetHandle(), redraw);
}
#if defined(OWL5_COMPAT)
//
/// Installs a pop-up menu in this window for automatic handling of the WM_CONTEXTMENU message.
/// If a context menu is already installed, it is replaced.
///
/// \code
/// const auto contextMenu = TMenu{GetModule()->GetHandle(), IDM_CONTEXT};
/// AssignContextMenu(new TPopupMenu{contextMenu}); // Makes deep copy of contextMenu.
/// \endcode
///
/// \note Ownership of the given menu is taken.
///
/// \deprecated This is a deprecated overload. Instead, use
/// TWindow::AssignContextMenu(std::unique_ptr<TPopupMenu>).
//
void TWindow::AssignContextMenu(TPopupMenu* m)
{
delete ContextPopupMenu;
ContextPopupMenu = m;
}
#endif
//
/// Installs a pop-up menu in this window for automatic handling of the WM_CONTEXTMENU message.
///
/// If a context menu is already installed, it is replaced.
///
/// Example usage:
///
/// \code
/// auto m = std::make_unique<TPopupMenu>(module, resId, submenuIndex);
/// AssignContextMenu(std::move(m));
/// \endcode
///
/// \returns The current context menu is returned, if any. If the return value is ignored, the
/// returned menu is automatically destructed and deallocated.
///
/// \sa GetContextMenu returns a pointer to the currently installed context menu.
//
auto TWindow::AssignContextMenu(std::unique_ptr<TPopupMenu> menu) -> unique_ptr<TPopupMenu>
{
const auto r = ContextPopupMenu;
ContextPopupMenu = menu.release(); // We take ownership.
return unique_ptr<TPopupMenu>{r}; // Give the caller ownership of the old menu.
}
//
/// The default message handler for WM_CONTEXTMENU.
/// Respond to a right button click or VK_APPS key press in the window.
/// If a context menu is set, display it.
//
void
TWindow::EvContextMenu(HWND child, int x, int y)
{
TPopupMenu* m = GetContextMenu();
if (!m)
{
DefaultProcessing();
return;
}
TPoint p(x, y);
bool invalidPos = (x < 0 && y < 0);
if (invalidPos)
{
// The message was probably generated by the VK_APPS key (sets x = y = -1).
// See the Windows API documentation for WM_CONTEXTMENU.
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms647592.aspx
// In this case, we open the menu in the upper left corner of the given window.
//
p = TPoint(0, 0);
::ClientToScreen(child, &p);
}
else
{
// Provide additional help support by reporting the click position to the main window.
//
THelpHitInfo hit(p, this);
GetApplication()->GetMainWindow()->HandleMessage(WM_OWLHELPHIT, 0, reinterpret_cast<TParam2>(&hit));
}
m->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, p, 0, GetHandle());
}
//
/// The default message handler for WM_ENTERIDLE.
//
void
TWindow::EvEnterIdle(uint source, HWND hWndDlg)
{
// If we're the main window, let it rip from the receiver
//
if (source == MSGF_DIALOGBOX) {
if (IsFlagSet(wfMainWindow))
IdleAction(0);
else {
TWindow* win = GetWindowPtr(hWndDlg);
if (win)
win->IdleAction(0);
}
}
// Let original proc. have a crack at msg.
//
DefaultProcessing();
}
//
/// Wrapper for Windows API.
/// Returns display and placement information (normal, minimized, and maximized) about the window.
/// Throws TXOwl on failure. To get extended error information, call GetLastError.
//
auto TWindow::GetWindowPlacement() const -> WINDOWPLACEMENT
{
PRECONDITION(GetHandle());
auto wp = WINDOWPLACEMENT{sizeof(WINDOWPLACEMENT)};
const auto r = ::GetWindowPlacement(GetHandle(), &wp);
if (!r) throw TXOwl{_T("TWindow::GetWindowPlacement failed")};
return wp;
}
//
/// Wrapper for Windows API.
/// Sets the window to a display mode and screen position. `place` points to a window
/// placement structure that specifies whether the window is to be hidden, minimized
/// or displayed as an icon, maximized, restored to a previous position, activated
/// in its current form, or activated and displayed in its normal position.
/// Throws TXOwl on failure. To get extended error information, call GetLastError.
//
void TWindow::SetWindowPlacement(const WINDOWPLACEMENT& place)
{
PRECONDITION(GetHandle());
auto wp = place;
wp.length = sizeof(WINDOWPLACEMENT);
const auto r = ::SetWindowPlacement(GetHandle(), &wp);
if (!r) throw TXOwl{_T("TWindow::SetWindowPlacement failed")};
}
#if defined(OWL5_COMPAT)
//
/// Overload for backwards compatibility.
/// Returns `true` if successful and `false` on failure.
/// To get extended error information, call GetLastError.
//
bool TWindow::GetWindowPlacement(WINDOWPLACEMENT* place) const
{
PRECONDITION(place);
try
{
*place = GetWindowPlacement();
return true;
}
catch (const TXOwl&)
{
return false;
}
}
//
/// Overload for backwards compatibility.
/// Returns `true` if successful and `false` on failure.
/// To get extended error information, call GetLastError.
//
bool TWindow::SetWindowPlacement(const WINDOWPLACEMENT* place)
{
PRECONDITION(place);
try
{
SetWindowPlacement(*place);
return true;
}
catch (const TXOwl&)
{
return false;
}
}
#endif
//
/// Overload for TRegion.
/// \note The system takes ownership of the region handle, hence you must pass a moveable argument.
/// For example, `SetWindowRgn(TRegion{...})` or `SetWindowRegion(std::move(rgn))`.
/// \sa TWindow::SetWindowRgn(HRGN, bool)
//
auto TWindow::SetWindowRgn(TRegion&& r, bool repaint) -> bool
{
PRECONDITION(GetHandle());
return ::SetWindowRgn(GetHandle(), r.Release(), repaint) != 0;
}
//
/// Retrieves the properties of the given scroll bar.
/// The 'scrollInfo' parameter must be properly initialized according
/// to the Windows API documentation for SCROLLINFO.
/// Returns true on success.
/// \note Wrapper for Windows API.
//
bool
TWindow::GetScrollInfo(int bar, SCROLLINFO* scrollInfo) const
{
PRECONDITION(GetHandle());
return ::GetScrollInfo(GetHandle(), bar, scrollInfo);
}
//
/// Function-style overload
/// Returns selected properties of the given scroll bar.
/// Valid values for the 'mask' parameter are the same as for the
/// SCROLLINFO::fMask member documented by the Windows API.
/// \note On failure, SCROLLINFO::nMin, nMax, nPage, nPos and nTrackPos
/// are all left value-initialized (0).
//
SCROLLINFO
TWindow::GetScrollInfo(int bar, uint mask) const
{
SCROLLINFO i = {sizeof(SCROLLINFO), mask};
bool r = GetScrollInfo(bar, &i);
WARNX(OwlWin, !r, 0, _T("GetScrollInfo failed.")); InUse(r);
return i;
}
//
/// Sets the properties of the given scroll bar.
/// Returns the current position of the scroll bar thumb.
/// \note Wrapper for Windows API.
//
int
TWindow::SetScrollInfo(int bar, SCROLLINFO* scrollInfo, bool redraw)
{
PRECONDITION(GetHandle());
return ::SetScrollInfo(GetHandle(), bar, scrollInfo, redraw);
}
//
/// Returns the thumb position in the scroll bar. The position returned is relative
/// to the scrolling range. If bar is SB_CTL, it returns the position of a control
/// in the scroll bar. If bar is SB_HORZ, it returns the position of a horizontal
/// scroll bar. If bar is SB_VERT, it returns the position of a vertical scroll bar.
//
int
TWindow::GetScrollPos(int bar) const
{
return GetScrollInfo(bar, SIF_POS).nPos;
}
//
/// Sets the thumb position in the scroll bar. Parameter 'bar' identifies the position
/// (horizontal, vertical, or scroll bar control) to return and can be one of the
/// SB_Xxxx scroll bar constants.
/// Returns the current position of the scroll bar thumb.
//
int
TWindow::SetScrollPos(int bar, int pos, bool redraw)
{
SCROLLINFO i = {sizeof(SCROLLINFO), SIF_POS, 0, 0, 0, pos};
return SetScrollInfo(bar, &i, redraw);
}
//
/// Returns the thumb track position in the scroll bar. Call this function only during
/// the processing of a scroll message with the SB_THUMBTRACK or SB_THUMBPOSITION code.
/// See GetScrollPos for valid values for the 'bar' parameter.
//
int
TWindow::GetScrollTrackPos(int bar) const
{
return GetScrollInfo(bar, SIF_TRACKPOS).nTrackPos;
}
//
/// Returns the minimum and maximum positions in the scroll bar. If bar is SB_CTL,
/// it returns the position of a control in the scroll bar. If bar is SB_HORZ, it
/// returns the position of a horizontal scroll bar. If bar is SB_VERT, it returns
/// the position of a vertical scroll bar. minPos and maxPos hold the lower and
/// upper range, respectively, of the scroll bar positions. If there are no scroll
/// bar controls, or if the scrolls are non-standard, minPos and maxPos are zero.
//
void
TWindow::GetScrollRange(int bar, int& minPos, int& maxPos) const
{
SCROLLINFO i = GetScrollInfo(bar, SIF_RANGE);
minPos = i.nMin;
maxPos = i.nMax;
}
//
/// Function-style overload
//
TWindow::TScrollRange
TWindow::GetScrollRange(int bar) const
{
SCROLLINFO i = GetScrollInfo(bar, SIF_RANGE);
return TScrollRange(i.nMin, i.nMax);
}
//
/// Sets the thumb position in the scroll bar. bar identifies the position
/// (horizontal, vertical, or scroll bar control) to set and can be one of the
/// SB_Xxxx scroll bar constants. minPos and maxPos specify the lower and upper
/// range, respectively, of the scroll bar positions.
//
void
TWindow::SetScrollRange(int bar, int minPos, int maxPos, bool redraw)
{
SCROLLINFO i = {sizeof(SCROLLINFO), SIF_RANGE, minPos, maxPos};
SetScrollInfo(bar, &i, redraw);
}
//
/// Overload taking the range as a pair
//
void
TWindow::SetScrollRange(int bar, const TScrollRange& r, bool redraw)
{
SetScrollRange(bar, r.first, r.second, redraw);
}
//
/// Returns the page property (SCROLLINFO::nPage) of the given scroll bar.
//
int
TWindow::GetScrollPage(int bar) const
{
return GetScrollInfo(bar, SIF_PAGE).nPage;
}
//
/// Sets the page property (SCROLLINFO::nPage) of the given scroll bar.
//
void
TWindow::SetScrollPage(int bar, int page, bool redraw)
{
SCROLLINFO i = {sizeof(SCROLLINFO), SIF_PAGE, 0, 0, static_cast<UINT>(page)};
SetScrollInfo(bar, &i, redraw);
}
//
//
//
void
TWindow::EnableTooltip(bool enable)
{
if (!Tooltip) {
// To circumvent this scenario, we'll look for a window which is fairly
// stable/rooted as owner of the tooltip. Ideally, we'll get the
// application's main window.
//
TWindow* tipParent = this;
/* // check it what if window -> is closed but tooltip live?????????
// it is for gastget only ????????????????????
while (tipParent->GetParentO()){
tipParent = tipParent->GetParentO();
if (tipParent->IsFlagSet(wfMainWindow))
break;
}
*/
// Create and initialize tooltip
//
SetTooltip(new TTooltip(tipParent));
}
else {
if (Tooltip->GetHandle())
Tooltip->Activate(enable);
}
}
//
// Set a specific tooltip for this window. Assume we now own the ToolTip
//
void
TWindow::SetTooltip(TTooltip* tooltip)
{
// Cleanup; via Condemned list if tooltip was created
//
if (Tooltip) {
if (Tooltip->GetHandle())
Tooltip->SendMessage(WM_CLOSE);
else
delete Tooltip;
}
// Store new tooltip and create if necessary
//
Tooltip = tooltip;
if (Tooltip) {
if (!Tooltip->GetHandle()) {
// Make sure tooltip is disabled so it does not take input focus
Tooltip->Attr.Style |= WS_DISABLED;
Tooltip->Create();
}
}
}
//
/// Copies a window's update region into a region specified by region. If erase is
/// true, GetUpdateRgn erases the background of the updated region and redraws
/// nonclient regions of any child windows. If erase is false, no redrawing occurs.
/// If the call is successful, GetUpdateRgn returns a value indicating the kind of
/// region that was updated. If the region has no overlapping borders, it returns
/// SIMPLEREGION; if the region has overlapping borders, it returns COMPLEXREGION;
/// if the region is empty, it returns NULLREGION; if an error occurs, it returns
/// ERROR.
///
/// \note Not inline to avoid requiring gdiobjec.h by window.h just to get TRegion's
/// conversion operator
//
int
TWindow::GetUpdateRgn(TRegion& region, bool erase) const
{
PRECONDITION(GetHandle());
return ::GetUpdateRgn(GetHandle(), region, erase);
}
//
/// If a window can process dropped files, DragAcceptFiles sets accept to true.
/// \note Wrapper for Windows API.
//
void
TWindow::DragAcceptFiles(bool accept)
{
PRECONDITION(GetHandle());
TShell::DragAcceptFiles(GetHandle(), accept);
}
//
/// Creates and displays a message box that contains a message (text), a title
/// (caption), and icons or push buttons (type). If caption is 0, the default title
/// is displayed. Although flags is set to one push button by default, it can contain
/// a combination of the MB_Xxxx message constants. This function returns one of
/// the following constants:
/// - \c \b IDABORT User selected the abort button.
/// - \c \b IDCANCEL User selected the cancel button.
/// - \c \b IDIGNORE User selected the ignore button.
/// - \c \b IDNO User selected the no button.
/// - \c \b IDOK User selected the OK button
/// - \c \b IDRETRY User selected the retry button.
/// - \c \b IDYES User selected the yes button.
//
int
TWindow::MessageBox(LPCTSTR text, LPCTSTR caption, uint flags) const
{
PRECONDITION(GetApplication());
PRECONDITION(GetHandle());
return GetApplication()->MessageBox(*this, text, caption, flags);
}
int
TWindow::MessageBox(uint resId, LPCTSTR caption, uint flags) const
{
PRECONDITION(GetApplication());
PRECONDITION(GetHandle());
PRECONDITION(resId);
tstring text = LoadString(resId);
return MessageBox(text.c_str(), caption, flags);
}
int
TWindow::MessageBox(uint resId, const tstring& caption, uint flags) const
{
PRECONDITION(GetApplication());
PRECONDITION(GetHandle());
PRECONDITION(resId);
tstring text = LoadString(resId);
return MessageBox(text.c_str(), caption.c_str(), flags);
}
//
/// Displays a standard message box.
/// Wrapper for Windows API.
/// \sa http://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-messageboxindirecta
//
auto TWindow::MessageBoxIndirect(HINSTANCE resourceModule, TResId text, TResId caption, uint styleFlags, TResId icon, DWORD_PTR contextHelpId, MSGBOXCALLBACK callback, DWORD languageId) const -> int
{
auto p = MSGBOXPARAMS
{
sizeof(MSGBOXPARAMS),
GetHandle(),
resourceModule,
text,
caption,
styleFlags,
icon,
contextHelpId,
callback,
languageId
};
return ::MessageBoxIndirect(&p);
}
//
/// Displays a standard message box using the given icon.
/// The flag MB_USERICON is automatically combined with the given flags, so there is no need to specify it.
/// Wrapper for Windows API.
///
/// \note The specified icon resource is assumed to reside in the same module as the window, i.e. the
/// HINSTANCE handle passed to ::&MessageBoxIndirect is the value returned by `this->GetModule()->GetHandle()`.
///
/// \sa http://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-messageboxindirecta
//
auto TWindow::MessageBoxIndirect(TResId icon, const tstring& text, const tstring& caption, uint flags) const -> int
{
PRECONDITION(GetModule() && GetModule()->GetHandle());
return MessageBoxIndirect(
GetModule()->GetHandle(),
TResId{text.c_str()},
TResId{caption.c_str()},
flags | MB_USERICON,
icon,
0,
nullptr,
0
);
}
auto TWindow::FormatMessageBox(const tstring& formatString, const tstring& caption, uint flags, ...) const -> int
{
va_list argp;
va_start(argp, flags);
try
{
auto format = [](const tstring& fmt, va_list argp) -> tstring
{
auto buffer = array<tchar, 1024>{};
const auto r = _vsntprintf(buffer.data(), buffer.size(), fmt.c_str(), argp);
if (r == -1)
{
buffer.back() = _T('\0');
WARNX(OwlWin, r == -1, 0, _T("TWindow::FormatMessageBox: Message was truncated."));
}
return buffer.data();
};
const auto s = format(formatString, argp);
const auto r = MessageBox(s, caption, flags);
va_end(argp);
return r;
}
catch (...)
{
WARNX(OwlWin, true, 0, _T("TWindow::FormatMessageBox failed."));
va_end(argp);
throw;
}
}
//
//
//
void TWindow::SethAccel(HACCEL _hAccel)
{
HAccel = _hAccel;
}
//
/// For use with CopyText.
//
struct TWindowGetWindowText
{
const TWindow& win;
TWindowGetWindowText(const TWindow& w) : win(w) {}
int operator()(LPTSTR buf, int buf_size)
{return win.GetWindowText(buf, buf_size);}
};
//
/// String-aware overload
//
tstring
TWindow::GetWindowText() const
{
return CopyText(GetWindowTextLength(), TWindowGetWindowText(*this));
}
//
/// For use with CopyText.
//
struct TWindowGetDlgItemText
{
const TWindow& win;
int id;
TWindowGetDlgItemText(const TWindow& w, int id_) : win(w), id(id_) {}
int operator()(LPTSTR buf, int buf_size)
{return win.GetDlgItemText(id, buf, buf_size);}
};
//
/// String-aware overload
//
tstring
TWindow::GetDlgItemText(int childId) const
{
return CopyText(::GetWindowTextLength(GetDlgItem(childId)), TWindowGetDlgItemText(*this, childId));
}
//
//
//
#if defined(__TRACE) || defined(__WARN)
//namespace owl {
ostream& operator <<(ostream& os, const TWindow& w)
{
os << '(';
os << _OBJ_FULLTYPENAME(&w) << ',';
os << static_cast<void*>(w.GetHandle()) << ',';
if (w.GetCaption())
os << '\'' << w.GetCaption() << '\'' << ',';
if (w.GetParentO())
os << "id=" << w.GetId();
os << ')';
return os;
}
//} // OWL namespace
#endif
///////////////////////////////////////////////////////////////////////////////////////
//
TDrawItem* ItemData2DrawItem(ULONG_PTR data)
{
// 0. If 0
if(data==0)
return 0;
// 1. check to make sure the pointer is valid
if(IsBadReadPtr((void*)data, sizeof(TDrawItem)))
return 0; // quick escape
// 2. check signature -> starting from 5's byte
#if defined(_WIN64)
uint32* itemId = (uint32*)((uint8*)data+8);
#else
uint32* itemId = (uint32*)((uint8*)data+4);
#endif
if(*itemId != TDrawItemBase::drawId)
return 0;
// 3. check to make sure the VTable pointer is valid
if(IsBadReadPtr(*(void**)data, sizeof(void*)))
return 0; // quick escape
#if 0 //?? maby simple casting enauph after checking itemId??
TDrawItem* item;
try{
item = localTryCatchBlock(data);
}
catch(...){
return 0;
}
return item;
#else
return (TDrawItem*)(void*)data;
#endif
}
///////////////////////////////////////////////////////////////////////////////////////
//
// Converts the given integer to a string.
// TODO: Relocate somewhere else more convenient for reuse.
//
static tstring
ToString(int v)
{
tchar buf[64];
_stprintf(buf, _T("%d"), v);
return tstring(buf);
}
//
// Returns a string which kind-of identifies the window (used during autopsy
// and vivisection of dead/dying window).
//
static tstring
GetSuspectDescription(TWindow* w)
{
_USES_CONVERSION;
if (!w) return _T("");
tstring caption = w->GetCaption() ? w->GetCaption() : _T("");
tstring id = ToString(w->GetWindowAttr().Id);
tstring type = _A2W(_OBJ_FULLTYPENAME(w));
tstring s;
s += _T("\"") + caption + _T("\"");
s += _T(", ID: ") + id;
s += _T(", window ") + type;
s += _T(".");
return s;
}
//
// Formats an error message defined by the given resource id.
// Appends the last system error message if any.
//
// NB! Note that the TSystemMessage probably is too far from the problem site to give a
// meaningful error message; intermediate system calls have probably happened since
// the exception was thrown.
//
static tstring
MakeTXWindowMessage(TWindow* win, uint resId)
{
TSystemMessage m;
tstring s = TXOwl::MakeMessage(resId, GetSuspectDescription(win).c_str());
if (m.SysError() != ERROR_SUCCESS)
s += _T("\n\nSystem error: ") + m.SysMessage();
return s;
}
//
/// Constructs a TXWindow object with a default resource ID of IDS_INVALIDWINDOW.
//
TXWindow::TXWindow(TWindow* win, uint resId)
:
TXOwl(MakeTXWindowMessage(win, resId), resId),
Window(win)
{
}
//
/// Copy the exception object.
//
TXWindow::TXWindow(const TXWindow& src)
:
TXOwl(src),
Window(src.Window)
{
}
//
/// Unhandled exception.
///
/// Called if an exception caught in the window's message loop has not been handled.
/// Unhandled() deletes the window. This type of exception can occur if a window
/// cannot be created.
//
int
TXWindow::Unhandled(TModule* app, uint promptResId)
{
Window = 0;
return TXOwl::Unhandled(app, promptResId);
}
//
/// Clone the exception object for safe-throwing.
//
TXWindow*
TXWindow::Clone()
{
return new TXWindow(*this);
}
//
/// Throws the exception object. Throw() must be implemented in any class derived
/// from TXOwl.
//
void
TXWindow::Throw()
{
throw *this;
}
//
/// Creates the TXWindow exception and throws it.
//
void
TXWindow::Raise(TWindow* win, uint resourceId)
{
TXWindow(win, resourceId).Throw();
}
//
// Returns the window causing the exception.
//
TWindow*
TXWindow::GetWindow() const
{
return Window;
}
} // OWL namespace
/* ========================================================================== */
↑ V1027 Pointer to an object of the 'TNotify' class is cast to unrelated 'TTooltipText' class.
↑ V641 The size of the '& _not' buffer is not a multiple of the element size of the type 'TTooltipText'.
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: Parent, Attr, Scroller, HAccel, CursorModule, HCursor, ...