//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1992, 1997 by Borland International, All Rights Reserved
//
/// \file
/// Implementation of class TGadgetWindow and TGadgetWindowFont.
//----------------------------------------------------------------------------
#include <owl/pch.h>
#include <owl/gadget.h>
#include <owl/gadgetwi.h>
#include <owl/framewin.h>
#include <owl/decframe.h>
#include <owl/celarray.h>
#include <owl/tooltip.h>
#include <owl/uimetric.h>
#include <owl/commctrl.h>
#include <owl/theme.h>
#if defined(__TRACE) || defined(__WARN)
# if !defined(OWL_PROFILE_H)
# include <owl/profile.h>
# endif
#endif
#include <stdio.h>
#include <algorithm>
using namespace std;
namespace owl {
//
// Diagnostic group for gadgets & gadget windows
//
OWL_DIAGINFO;
DIAG_DEFINE_GROUP_INIT(OWL_INI, OwlGadget, 1, 0);
DIAG_DECLARE_GROUP(OwlCmd);
const uint GadgetWindowTimerID = 0xBACA;
const uint GadgetWindowTimeOut = 1000;
DEFINE_RESPONSE_TABLE1(TGadgetWindow, TWindow)
EV_WM_ERASEBKGND,
EV_WM_LBUTTONDOWN,
EV_WM_LBUTTONUP,
EV_WM_LBUTTONDBLCLK,
EV_WM_RBUTTONDOWN,
EV_WM_RBUTTONUP,
EV_WM_MOUSEMOVE,
EV_WM_WINDOWPOSCHANGING,
EV_WM_SYSCOLORCHANGE,
EV_WM_CREATETOOLTIP,
END_RESPONSE_TABLE;
uint TGadgetWindow::FlatStyle = NonFlatNormal;
namespace {
//
// Returns a font variation based on the default TGadgetWindowFont.
//
LOGFONT MakeGadgetWindowFont_(int pointSize, bool bold, bool italic)
{
PRECONDITION(pointSize > 0);
LOGFONT f = TGadgetWindowFont().GetObject();
f.lfHeight = -pointSize * TScreenDC().GetDeviceCaps(LOGPIXELSY) / 72;
f.lfWeight = bold ? FW_BOLD : FW_NORMAL;
f.lfItalic = italic ? TRUE : FALSE;
return f;
}
} // namespace
//
/// Constructs a font based on TDefaultGuiFont.
//
TGadgetWindowFont::TGadgetWindowFont()
: TFont(TDefaultGuiFont())
{}
//
/// Constructs a font with the given size, boldness and italics style.
/// Note that the size should be given in points, not pixels.
/// The font is based on the system font encapsulated by TDefaultGuiFont.
//
TGadgetWindowFont::TGadgetWindowFont(int pointSize, bool bold, bool italic)
: TFont(MakeGadgetWindowFont_(pointSize, bold, italic))
{}
//----------------------------------------------------------------------------
//
/// Creates a TGadgetWindow interface object with the default tile direction and
/// font and passes module with a default value of 0.
//
TGadgetWindow::TGadgetWindow(TWindow* parent,
TTileDirection direction,
TFont* font,
TModule* module)
:
WantTimer(false),TimerID(0), ThemeBackgroundMode(false)
{
// Initialize virtual base, in case the derived-most used default ctor
//
TWindow::Init(parent, 0, module);
// Make sure we don't paint or erase over any child windows
//
ModifyStyle(0,WS_CLIPCHILDREN);
Capture = 0;
AtMouse = 0;
Font = font ? font : new TGadgetWindowFont();
Direction = direction;
// Shrink wrap the narrow axis
//
#if defined(BI_COMP_GNUC)
TTileDirection dir = Direction;
ShrinkWrapWidth = dir != Horizontal;// As narrow as possible
ShrinkWrapHeight = dir != Vertical; // As short as possible
#else
ShrinkWrapWidth = ((int)Direction) != Horizontal;// As narrow as possible
ShrinkWrapHeight = ((int)Direction) != Vertical; // As short as possible
#endif
HintMode = PressHints;
WideAsPossible = 0; // Number of gadgets with "WideAsPossible" set
DirtyLayout = true;
// Compute the font height
//
TEXTMETRIC metrics;
Font->GetTextMetrics(metrics);
FontHeight = metrics.tmHeight + metrics.tmExternalLeading;
// Choose a default height based on the height of the font plus room
// for a top and bottom border.
// !CQ Really only useful for Horizontal layout if at all...
//
Attr.H = FontHeight;
if (Attr.Style & WS_BORDER)
Attr.H += 2 * TUIMetric::CyBorder;
SetBkgndColor(TColor::Sys3dFace);
SharedCels = 0;
Tooltip = 0;
WantTooltip = false;
// Rectangular layout members
// !CQ Use Attr.W now for desired width?
//
RowWidth = 0; // Don't know yet? Set externally. Never let get smaller than widest gadget
RowMargin = 4;
}
//
/// Destructs this gadget window. Deletes the gadgets, the font and the shared
/// cel array
//
TGadgetWindow::~TGadgetWindow()
{
delete Font;
delete SharedCels;
}
//
/// Responds to WM_SYSCOLORCHANGE to let the gadgets update their UI colors, and to
/// let this gadget window update its background color.
///
/// \note This is an obsolete function retained for compatibility. New TWindow,
/// TColor, and other UI support makes this unecessary.
//
void
TGadgetWindow::EvSysColorChange()
{
for (TGadget* g = Gadgets; g; g = g->NextGadget())
g->SysColorChange();
}
//
/// During idle time, iterates over the Gadgets invoking their CommandEnable()
/// member function. Also detects lost mouse up for fly-over hint mode.
//
bool
TGadgetWindow::IdleAction(long idleCount)
{
if (idleCount == 0) {
// See if we missed a mouse move & still need to send a MouseLeave to a
// gadget
//
if (AtMouse) {
TPoint crsPoint;
GetCursorPos(crsPoint);
if (WindowFromPoint(crsPoint) != GetHandle())
HandleMessage(WM_MOUSEMOVE, 0, MkParam2(-1,-1)); // nowhere
}
}
// Let the gadgets do command enabling if they need to
//
for (TGadget* g = Gadgets; g; g = g->NextGadget())
g->IdleAction(idleCount);
// Chain to base implementation
//
return TWindow::IdleAction(idleCount);
}
//
/// Inform GadgetWindow to start a Timer notification.
///
/// Set timer - useful for gadgets that need regular update [Time/Date].
///
/// \note TGadgetWindow does not catch the WM_TIMER event. The purpose of
/// the timer message it to cause message dispatching which eventually
/// leads to an 'IdleAction' call.
//
bool
TGadgetWindow::EnableTimer()
{
// Set flag and let 'SetupWindow' create timer
//
if (!GetHandle()) {
WantTimer = true;
return true;
}
// Set a timer if one's not enabled already
//
if (!TimerID)
TimerID = SetTimer(GadgetWindowTimerID, GadgetWindowTimeOut);
return TimerID != 0;
}
//
/// Sets the width and height of the data members. By default, if the tile direction
/// is horizontal, ShrinkWrapWidth is false and ShrinkWrapHeight is true. Also by
/// default, if the direction is vertical, ShrinkWrapWidth is true and
/// ShrinkWrapHeight is false.
//
void
TGadgetWindow::SetShrinkWrap(bool shrinkWrapWidth, bool shrinkWrapHeight)
{
ShrinkWrapWidth = shrinkWrapWidth;
ShrinkWrapHeight = shrinkWrapHeight;
}
//
/// Called by a gadget that wants to capture the mouse
///
/// GadgetSetCapture reserves all mouse messages for the gadget window until the
/// capture is released. Although gadgets are always notified if a left button-down
/// event occurs within the rectangle, the derived gadget class must call
/// GadgetSetCapture if you want the gadget to be notified when a mouse drag and a
/// mouse button-up event occurs.
///
/// Fails if already captured
//
bool
TGadgetWindow::GadgetSetCapture(TGadget& gadget)
{
if (Capture)
return false;
else {
Capture = &gadget;
SetCapture();
return true;
}
}
//
/// Releases the capture so that other windows can receive mouse messages.
///
/// Ignores other gadgets
//
void
TGadgetWindow::GadgetReleaseCapture(TGadget& gadget)
{
if (&gadget == Capture) {
Capture = 0;
ReleaseCapture();
}
}
//
/// Simulates menu selection messages so that ObjectWindows command processing can
/// display command hints for the given command id (CM_xxxx).
//
void
TGadgetWindow::SetHintCommand(int id)
{
// Find our ancestor decorated frame [which potentially has a statusbar
// to show hint messages]
//
TWindow* parent= GetParentO();
TDecoratedFrame* frame= parent ?
TYPESAFE_DOWNCAST(parent, TDecoratedFrame) : 0;
while (parent && !frame) {
parent = parent->GetParentO();
if (parent)
frame = TYPESAFE_DOWNCAST(parent, TDecoratedFrame);
}
// Forward command id to frame via fake WM_MENUSELECT messages
//
if (frame) {
if (id > 0) {
frame->HandleMessage(WM_MENUSELECT, id, 0);
}
else {
// Send a menuselect w/ flags==0xFFFF and hMenu==0 to indicate end
//
frame->HandleMessage(WM_MENUSELECT, 0xFFFF0000, 0); // flags+item, hmenu
}
frame->HandleMessage(WM_ENTERIDLE, MSGF_MENU);
}
}
//
/// Sets or changes the margins for the gadget window and calls LayoutSession().
//
void
TGadgetWindow::SetMargins(const TMargins& margins)
{
Margins = margins;
LayoutSession();
}
//
/// Converts layout units to pixels. A layout unit is determined by dividing the
/// window font height by eight.
//
int
TGadgetWindow::LayoutUnitsToPixels(int units)
{
const long unitsPerEM = 8;
return int((long(units) * FontHeight + unitsPerEM / 2) / unitsPerEM);
}
//
/// LayoutSession is typically called when a change occurs in the size of the
/// margins or gadgets, or when gadgets are added or deleted. LayoutSession calls
/// TileGadgets() to tile the gadgets in the specified direction and Invalidate() to
/// mark the area as invalid (needs repainting).
//
void
TGadgetWindow::LayoutSession()
{
if (GetHandle())
InvalidateRect(TileGadgets());
}
//
/// Get the desired size for this gadget window by performing a trial layout of
/// the gadgets without touching them.
///
/// If shrink-wrapping was requested, GetDesiredSize returns the size needed to
/// accommodate the borders and the margins of the widest and highest gadget;
/// otherwise, it returns the width and height in the window's Attr structure.
///
/// If you want to leave extra room for a specific look (for example, a separator
/// line between gadgets, a raised line, and so on), you can override this function.
/// However, if you override GetDesiredSize(), you will probably also need to override
/// GetInnerRect() to calculate your custom inner rectangle.
///
/// For Rectangular layout it is assumed that the EndOfRow gadgets are already
/// Set.
//
void
TGadgetWindow::GetDesiredSize(TSize& size)
{
TLayoutInfo layout(NumGadgets);
if (ShrinkWrapWidth || ShrinkWrapHeight)
LayoutGadgets(Direction, layout);
// Calculate the X dimension for both shrinkWrap and externally set
//
if (ShrinkWrapWidth)
size.cx = layout.DesiredSize.cx;
else
size.cx = Attr.W; // Not shrink wrapped, just use set size
// Calculate the Y dimension for both shrinkWrap and externally set
// Always shrinkwrap rectangular layout vertically
//
if (ShrinkWrapHeight) {
if (NumGadgets == 0)
size.cy = 0;
else
size.cy = layout.DesiredSize.cy;
}
else
size.cy = Attr.H; // Not shrink wrapped, just use set size
}
//
/// Updates the Size in Attr.W and Attr.H to match that obtained using
/// GetDesiredSize() for each dimension that is shrink-wrapped.
//
void
TGadgetWindow::UseDesiredSize()
{
if (ShrinkWrapWidth || ShrinkWrapHeight) {
TSize size;
GetDesiredSize(size);
if (ShrinkWrapWidth)
Attr.W = size.cx;
if (ShrinkWrapHeight)
Attr.H = size.cy;
}
}
//
/// Overrides TWindow member function and chooses the initial size of the gadget if
/// shrink-wrapping was requested.
//
bool
TGadgetWindow::Create()
{
UseDesiredSize();
return TWindow::Create();
}
//
/// Sets the horizontal or vertical orientation of the gadgets. If the gadget window
/// is already created, SetDirection readjusts the dimensions of the gadget window
/// to fit around the gadgets.
///
/// The setting of the direction parameter is also related to the setting of the
/// second parameter (TLocation) in TDecoratedFrame's Insert function,. which
/// specifies where the decoration is added in relation to the frame window's client
/// window. If the second parameter in TDecoratedFrame::Insert() is set to top or
/// bottom, the direction parameter in SetDirection must be horizontal. If the
/// second parameter in TDecoratedFrame::Insert() is set to left or right, the
/// direction parameter in SetDirection must be vertical.
//
void
TGadgetWindow::SetDirection(TTileDirection direction)
{
#if defined(BI_COMP_GNUC)
TTileDirection dir = Direction;
if (dir != direction || DirtyLayout) {
if (dir != direction) {
#else
if (((int)Direction) != direction || DirtyLayout) {
// Swap margin's and ShrinkWrap's X & Y axis
//
if (((int)Direction) != direction) {
#endif
// Margin swapping seems to not be used in the latest UIs
//
#if 0
int t = Margins.Left; Margins.Left = Margins.Top; Margins.Top = t;
t = Margins.Right; Margins.Right = Margins.Bottom; Margins.Bottom = t;
#endif
// NOTE: a limitation. Passing thru Rectangular will mess this swap up
//
bool sww = ShrinkWrapWidth;
ShrinkWrapWidth = ShrinkWrapHeight;
ShrinkWrapHeight = sww;
Direction = direction;
DirtyLayout = true;
}
// Get & use the new size & relayout if created
//
UseDesiredSize();
LayoutSession();
}
}
//
/// Set flat style options, or disable it.
//
void
TGadgetWindow::EnableFlatStyle(uint style)
{
if(style == FlatDefault)
{
style = FlatStandard | FlatSingleDiv | FlatGrayButtons | FlatHotText;
static const auto v = GetCommCtrlVersion();
if (v >= 0x60000)
style |= FlatXPTheme;
}
FlatStyle = style;
}
//
// Returns true if the application is themed (Windows XP Visual Style).
//
bool
TGadgetWindow::IsThemed() const
{
return (FlatStyle & FlatXPTheme) && TThemeModule::GetInstance().IsAppThemed();
}
//
// Enables or disables themed background.
//
void
TGadgetWindow::EnableThemeBackground(bool enable)
{
ThemeBackgroundMode = enable;
}
//
// Returns true if themed background is enabled.
//
bool
TGadgetWindow::IsThemeBackgroundEnabled() const
{
return ThemeBackgroundMode;
}
//
// Returns true if themed background is enabled and themes are active.
//
bool
TGadgetWindow::IsBackgroundThemed() const
{
return IsThemeBackgroundEnabled() && IsThemed();
}
//
/// Sets the maximum width for each row used for rectangular layout.
/// LayoutSession() or SetDirection() must be called for changes to take effect
//
void
TGadgetWindow::SetRectangularDimensions(int width, int /*height*/, int rowMargin)
{
if (width != RowWidth || (rowMargin >= 0 && rowMargin != RowMargin))
{
RowWidth = width;
if (rowMargin >= 0)
RowMargin = rowMargin;
if (Direction == Rectangular)
DirtyLayout = true;
}
}
//
/// GetInnerRect computes the rectangle inside of the borders and margins of the
/// gadget.
///
/// If you want to leave extra room for a specific look (for example, a separator
/// line between gadgets, a raised line, and so on), you can override this function.
/// If you override GetInnerRect, you will probably also need to override
/// GetDesiredSize() to calculate your custom total size.
//
void
TGadgetWindow::GetInnerRect(TRect& rect)
{
int left, right, top, bottom;
GetMargins(Margins, left, right, top, bottom);
if (Attr.W != 0) {
rect.left = left;
rect.right = Attr.W - right;
if (Attr.Style & WS_BORDER)
rect.right -= 2 * TUIMetric::CyBorder;
}
else {
rect.left = 0;
rect.right = 0;
}
if (Attr.H != 0) {
rect.top = top;
rect.bottom = Attr.H - bottom;
if (Attr.Style & WS_BORDER)
rect.bottom -= 2 * TUIMetric::CxBorder;
}
else {
rect.top = 0;
rect.bottom = 0;
}
}
//
/// Virtual called by TileGadgets() for each gadget in order to give derived
/// classes a chance to modify the gadget positioning. Default is to overlap
/// adjacent plain-bordered gadgets
//
void
TGadgetWindow::PositionGadget(TGadget* previous, TGadget* next, TPoint& origin)
{
// Overlap the button borders
// !CQ make sure this is right for win95. Also in regular controlbar
//
if (previous->GetBorderStyle() == TGadget::Plain &&
next->GetBorderStyle() == TGadget::Plain)
{
if (Direction == Horizontal)
origin.x -= TUIMetric::CxBorder;
else
origin.y -= TUIMetric::CyBorder;
}
}
//
/// Tiles the gadgets in the direction requested (horizontal or vertical). Calls
/// PositionGadget() to give derived classes an opportunity to adjust the spacing
/// between gadgets in their windows.
//
TRect
TGadgetWindow::TileGadgets()
{
TLayoutInfo layout(NumGadgets);
LayoutGadgets(Direction, layout);
TRect invalidRect(0,0,0,0);
int i = 0;
for (TGadget* gadget = Gadgets; gadget; gadget = gadget->Next, i++) {
TRect originalBounds = gadget->GetBounds();
TRect bounds = layout.GadgetBounds[i];
if (originalBounds != bounds) {
gadget->SetBounds(bounds);
if (originalBounds.TopLeft() != TPoint(0, 0))
invalidRect |= originalBounds;
invalidRect |= bounds;
}
}
DirtyLayout = false;
return invalidRect;
}
//
/// Helper for LayoutGadgets() to calculate horizontal tiling
//
void
TGadgetWindow::LayoutHorizontally(TLayoutInfo& layout)
{
TRect innerRect;
GetInnerRect(innerRect);
int leftM, rightM, topM, bottomM;
GetMargins(Margins, leftM, rightM, topM, bottomM);
layout.DesiredSize = TSize(0,0);
layout.GadgetBounds = new TRect[NumGadgets];
// First pass tally the width of all gadgets that don't have "WideAsPossible"
// set if any have it seet so that we can divide up the extra width
//
// NOTE: we must also take into account any adjustments to the gadget spacing
//
int wideAsPossibleWidth;
if (WideAsPossible) {
int width = 0;
for (TGadget* gadget = Gadgets; gadget; gadget = gadget->Next) {
if (!gadget->WideAsPossible) {
TSize desiredSize(0, 0);
gadget->GetDesiredSize(desiredSize);
width += desiredSize.cx;
}
if (gadget->Next) {
TPoint spacing(0, 0);
PositionGadget(gadget, gadget->Next, spacing);
width += spacing.x;
}
}
wideAsPossibleWidth = std::max(( (innerRect.Width() - width) / (int)WideAsPossible ), 0);
}
else
wideAsPossibleWidth = 0;
// Now tile all the gadgets
//
int x = leftM;
int y = topM;
int h = 0;
int i = 0;
// Pass 1: calculate the gadget sizes and row height
//
TGadget* gadget;
for (gadget = Gadgets; gadget; gadget = gadget->Next, i++) {
TSize desiredSize(0, 0);
gadget->GetDesiredSize(desiredSize);
// Stash away the desiredSize for the next pass
//
layout.GadgetBounds[i].left = desiredSize.cx;
layout.GadgetBounds[i].top = desiredSize.cy;
h = std::max(h, (int)desiredSize.cy);
}
// Pass 2: place the gadget in the row, centered vertically
//
i = 0;
for (gadget = Gadgets; gadget; gadget = gadget->Next, i++) {
TRect bounds = gadget->GetBounds();
// Recover desired size from where we stashed it
//
TSize desiredSize(layout.GadgetBounds[i].left, layout.GadgetBounds[i].top);
bounds.left = x;
bounds.top = y + (h - desiredSize.cy) / 2; // Center vertically
if (gadget->WideAsPossible)
bounds.right = bounds.left + std::max(wideAsPossibleWidth, (int)desiredSize.cx);
else
bounds.right = bounds.left + desiredSize.cx;
bounds.bottom = bounds.top + desiredSize.cy;
layout.GadgetBounds[i] = bounds;
x += bounds.Width();
if (gadget->Next) {
TPoint origin(x, 0);
PositionGadget(gadget, gadget->Next, origin);
x = origin.x;
}
// Update gadget window's cumulative desired size
//
layout.DesiredSize.cx = std::max(int(bounds.right+rightM), (int)layout.DesiredSize.cx);
layout.DesiredSize.cy = std::max(int(bounds.bottom+bottomM), (int)layout.DesiredSize.cy);
}
}
//
/// Helper for LayoutGadgets() to calculate vertical tiling
//
void
TGadgetWindow::LayoutVertically(TLayoutInfo& layout)
{
TRect innerRect;
GetInnerRect(innerRect);
int leftM, rightM, topM, bottomM;
GetMargins(Margins, leftM, rightM, topM, bottomM);
layout.DesiredSize = TSize(0,0);
layout.GadgetBounds = new TRect[NumGadgets];
// Now tile all the gadgets
//
int y = topM;
int x = leftM;
int w = 0;
int i = 0;
// Pass 1: calculate gadget sizes and column width
//
TGadget* gadget;
for (gadget = Gadgets; gadget; gadget = gadget->Next, i++) {
TSize desiredSize(0, 0);
gadget->GetDesiredSize(desiredSize);
// Stash away the DesiredSize for the next pass
//
layout.GadgetBounds[i].left = desiredSize.cx;
layout.GadgetBounds[i].top = desiredSize.cy;
w = std::max(w, (int)desiredSize.cx);
}
// Pass 2: place the gadget in the column, centered horizontally
//
i = 0;
for (gadget = Gadgets; gadget; gadget = gadget->Next, i++) {
TRect bounds = gadget->GetBounds();
// Recover desired size from where we stashed it
//
TSize desiredSize(layout.GadgetBounds[i].left, layout.GadgetBounds[i].top);
bounds.top = y;
bounds.bottom = bounds.top + desiredSize.cy;
bounds.left = x + (w - desiredSize.cx) / 2; // Center horizontally
bounds.right = bounds.left + desiredSize.cx;
layout.GadgetBounds[i] = bounds;
y += bounds.Height();
if (gadget->Next) {
TPoint origin(0, y);
PositionGadget(gadget, gadget->Next, origin);
y = origin.y;
}
// Update gadget window's desired size
//
layout.DesiredSize.cx = (int)std::max(int(bounds.right+rightM), (int)layout.DesiredSize.cx);
layout.DesiredSize.cy = (int)std::max(int(bounds.bottom+bottomM), (int)layout.DesiredSize.cy);
}
}
//
/// Assign top & bottoms of all controls on this row (i.e. from rowStart to
/// lastBreak). If !lastBreak, go to the end of the list.
//
void TGadgetWindow::FinishRow(int istart, TGadget* rowStart, TGadget* lastBreak,
int rowTop, TLayoutInfo& layout, int& rowHeight)
{
int j;
TGadget* g;
rowHeight = 0;
for (j = istart, g = rowStart; g; g = g->Next, j++) {
rowHeight = std::max(rowHeight, (int)layout.GadgetBounds[j].bottom);
if (g == lastBreak)
break;
}
for (j = istart, g = rowStart; g; g = g->Next, j++) {
// bounds.bottom has DesiredSize.cy
layout.GadgetBounds[j].top = rowTop + (rowHeight - layout.GadgetBounds[j].bottom) / 2;
layout.GadgetBounds[j].bottom = layout.GadgetBounds[j].top + layout.GadgetBounds[j].bottom;
//JJH - max can compare only equal types
#if defined(__GNUC__)
layout.DesiredSize.cy = max((LONG)layout.GadgetBounds[j].bottom, layout.DesiredSize.cy);
#else
layout.DesiredSize.cy = max(layout.GadgetBounds[j].bottom, layout.DesiredSize.cy);
#endif
if (g == lastBreak)
break;
}
}
//
/// Helper for LayoutGadgets() to calculate rectangular 2D tiling
//
void
TGadgetWindow::LayoutRectangularly(TLayoutInfo& layout)
{
TRect innerRect;
GetInnerRect(innerRect);
layout.DesiredSize = TSize(0,0);
layout.GadgetBounds = new TRect[NumGadgets];
// !CQ memset((void*)layout.GadgetBounds, 0, sizeof(TRect) * NumGadgets);// Debugging code
int leftM, rightM, topM, bottomM;
GetMargins(Margins, leftM, rightM, topM, bottomM);
// Now tile all the gadgets. Assume no wide-as-possibles.
//
int x = leftM;
int y = topM;
int right = RowWidth - rightM; // Base right margin limit on RowWidth
// If any controls are wider than right margin, push out the right margin.
//
TGadget* gadget;
for (gadget = Gadgets; gadget; gadget = gadget->Next) {
TSize desiredSize;
gadget->GetDesiredSize(desiredSize);
// SIR June 20th 2007 max instead of ::max
right = max(right, (int)(x + desiredSize.cx + rightM));
}
// Scan gadgets, positioning & placing all of the EndOfRow flags
//
TGadget* rowStart; // Tracks the first gadget in the row
TGadget* lastBreak; // Tracks the last gadget in the row
bool contRow = false; // Working on a group continuation row
bool contBreak = false; // Finished group on continuation row
int i = 0;
int istart = 0;
int iend = 0; // Tracks the last visible gadget in the row
int ibreak = 0;
int rowHeight;
rowStart = Gadgets;
lastBreak = Gadgets;
for (gadget = Gadgets; gadget; gadget = gadget->Next, i++) {
gadget->SetEndOfRow(false);
// !CQ ignore wide-as-possible gadget requests
//
TSize desiredSize;
gadget->GetDesiredSize(desiredSize);
TRect bounds = gadget->GetBounds();
// Do the horizontal layout of this control
//
bounds.left = x;
bounds.right = bounds.left + desiredSize.cx;
// Store gadget's height in bottom, so we can calculate the row height
// later
//
bounds.top = 0;
bounds.bottom = desiredSize.cy;
// If too big or a new group on a group continue row, (& is not the first
// & is visible) then back up to iend & reset to lastBreak+1
//
if ((bounds.right > right || contBreak) &&
gadget != rowStart && gadget->IsVisible()) {
lastBreak->SetEndOfRow(true);
// Update gadget window's desired size
//
layout.DesiredSize.cx =
std::max((layout.GadgetBounds[iend].right+rightM), layout.DesiredSize.cx);
// Do the vertical layout of this row
//
FinishRow(istart, rowStart, lastBreak, y, layout, rowHeight);
contRow = lastBreak->IsVisible(); // Next row is a group continuation
x = leftM;
y += rowHeight;
if (!contRow)
y += RowMargin;
gadget = lastBreak; // will get bumped to Next by for incr
i = ibreak; // so will this
rowStart = lastBreak = gadget->Next; // Begin next row
istart = i+1;
contBreak = false;
continue;
}
layout.GadgetBounds[i] = bounds;
// Advance the break and end cursors.
//
if (gadget->IsVisible()) {
if (lastBreak->IsVisible()) { // advance both if in the first group
iend = i;
lastBreak = gadget;
ibreak = i;
}
else if (!gadget->Next || !gadget->Next->IsVisible()) {
// advance end if next is a break.
iend = i;
}
}
else { // advance last break if this gadget is a break
lastBreak = gadget;
ibreak = i;
if (contRow) // This invisible gadget signifies end of group
contBreak = true;
}
x += bounds.Width();
if (gadget->Next) {
TPoint origin(x, 0);
PositionGadget(gadget, gadget->Next, origin);
x = origin.x;
}
}
// Update gadget window's desired size
//
layout.DesiredSize.cx =
(int)std::max(int(layout.GadgetBounds[iend].right+rightM), (int)layout.DesiredSize.cx);
// Do the vertical layout of the last row & add in bottom margin
//
FinishRow(istart, rowStart, 0, y, layout, rowHeight);
layout.DesiredSize.cy += bottomM;
}
//
/// Calculate the layout of the Gadgets in a given direction
//
void
TGadgetWindow::LayoutGadgets(TTileDirection dir, TLayoutInfo& layout)
{
switch (dir) {
case Horizontal:
LayoutHorizontally(layout);
break;
case Vertical:
LayoutVertically(layout);
break;
default:
//case Rectangular:
CHECK(dir == Rectangular);
LayoutRectangularly(layout);
break;
}
}
//
/// Used to notify the gadget window that a gadget has changed its size,
/// GadgetChangedSize calls LayoutSession() to re-layout all gadgets.
//
void
TGadgetWindow::GadgetChangedSize(TGadget&)
{
LayoutSession();
}
//
/// Inserts a gadget before or after a sibling gadget (TPlacement). If sibling is 0,
/// then the new gadget is inserted at either the beginning or the end of the gadget
/// list. If this window has already been created, LayoutSession() needs to be called
/// after inserting gadgets.
//
void
TGadgetWindow::Insert(TGadget& gadget, TPlacement placement, TGadget* sibling)
{
TGadgetList::Insert(gadget, placement, sibling);
// Compute the maxWidth and maxHeight of the gadgetwindow. Needed early when
// not created if gadget window is being positioned within another window.
//
// !CQ is this really needed? Seems very time consuming after every insert
// !CQ wait until all done inserting. Maybe only do if gadgetwindow is created
// !CQ have docking slip ask (via GetDesiredSize) when it needs initial dimensions
//
UseDesiredSize();
}
//
/// Inserts a list of Gadgets.
///
/// The caller needs to also call LayoutSession() after inserting gadgets if
/// this window has already been created.
//
void
TGadgetWindow::InsertFrom(TGadgetList& _list, TPlacement placement, TGadget* sibling)
{
TGadgetList::InsertFrom(_list, placement, sibling);
// Compute the maxWidth and maxHeight of the gadgetwindow. Needed early when
// not created if gadget window is being positioned within another window.
//
// !CQ is this really needed? Seems very time consuming after every insert
// !CQ wait until all done inserting. Maybe only do if gadgetwindow is created
// !CQ have docking slip ask (via GetDesiredSize) when it needs initial dimensions
//
UseDesiredSize();
}
//
/// A gadget has been inserted into this gadget window
//
void
TGadgetWindow::Inserted(TGadget& gadget)
{
// Let gadget know that it is now in this window
//
gadget.Window = this;
gadget.Inserted();
// If the gadgetwindow was already created, inform gadget
// so it may perform initialization that requires an HWND
//
if (GetHandle())
gadget.Created();
if (gadget.WideAsPossible)
WideAsPossible++;
}
//
/// Removes a gadget from the gadget window. The gadget is returned but not
/// destroyed. Remove returns 0 if the gadget is not in the window.
///
/// If this window has already been created, the calling application must call
/// LayoutSession after any gadgets have been removed.
//
TGadget*
TGadgetWindow::Remove(TGadget& gadget)
{
if (gadget.Window != this)
return 0;
// Perform actual removal from list
//
return TGadgetList::Remove(gadget);
}
//
/// This indicates that a gadget has been removed from this gadget window.
//
void
TGadgetWindow::Removed(TGadget& gadget)
{
// Re-adjust gadget now that it doesn't live in a window
//
if (gadget.WideAsPossible)
WideAsPossible--;
// Clear last know gadget as mouse location
//
if (&gadget == AtMouse)
AtMouse = 0;
// Release caption if it has/had it
//
GadgetReleaseCapture(gadget);
// Notify gadget and reset/adjust variables to reflect new state of gadget
//
gadget.Removed();
gadget.Window = 0;
gadget.Next = 0;
gadget.GetBounds() -= gadget.GetBounds().TopLeft();
}
//
/// Sets a new Shared CelArray for this gadget window. Allows a predefined array of
/// images to be shared by the gadgets. This GadgetWindow assumes ownership of the
/// passed CelArray pointer.
//
void
TGadgetWindow::SetCelArray(TCelArray* sharedCels)
{
delete SharedCels;
SharedCels = sharedCels;
}
//
/// Gets the Shared CelArray for this gadget window. Makes an empty one "on the fly"
/// if needed.
//
TCelArray&
TGadgetWindow::GetCelArray(int minX, int minY)
{
if (!SharedCels) {
// !CQ default??? BOGUS values here for now on purpose
if (!minX)
minX = 10;
if (!minY)
minY = 10;
SharedCels = new TCelArray(TSize(minX,minY), ILC_MASK, 10, 5);
}
return *SharedCels;
}
//
/// Overrides TWindow::SetupWindow to create tooltips for the gadget window.
//
void
TGadgetWindow::SetupWindow()
{
TWindow::SetupWindow();
// if 'WantTimer' is enabled, start a timer
//
if (WantTimer)
EnableTimer();
if (DirtyLayout) // !CQ latest OWL 5 was here...
LayoutSession();
// Now that this window is created, see if any of the gadgets have changed
// sizes (like control gadgets). If so, remember the size & relayout the
// gadgets. Also adjust the size of this gadget window to match what we want.
// !CQ should do or is done earlier? Like EvWindowPosChanging??
//
TSize size;
GetDesiredSize(size);
// if (DirtyLayout) // !CQ was here in old OWL 5, & Conrad's is here...
// LayoutSession();
if ((ShrinkWrapWidth && Attr.W != size.cx) || (ShrinkWrapHeight && Attr.H != size.cy))
{
if (ShrinkWrapWidth)
Attr.W = size.cx;
if (ShrinkWrapHeight)
Attr.H = size.cy;
LayoutSession();
SetWindowPos(0, 0, 0, size.X(), size.Y(),
SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);
}
// Create toolips after all other windows have been created.
///JGD This fixes problem with tooltips and dialog initially showing
///JGD up behind the main window (BTS 43768)
//
PostMessage( WM_OWLCREATETTIP );
}
//
// Override TWindow's virtual to cleanup potential Timer
//
void
TGadgetWindow::CleanupWindow()
{
// Cleanup pending timer
//
if (TimerID && KillTimer(TimerID)) {
TimerID = 0;
}
if (AtMouse)
{
AtMouse->MouseLeave(0, TPoint(-1, -1));
AtMouse = 0; //the window is destroyed => emulating 'mouse move at nowhere'
}
// Chain to base class' version
//
TWindow::CleanupWindow();
}
//
/// Relays 'interesting' messages to the tooltip window.
//
bool
TGadgetWindow::PreProcessMsg(MSG& msg)
{
// Check if this message might be worth relaying to the tooltip window
//
if (Tooltip && Tooltip->IsWindow()) {
if (msg.hwnd == *this || IsChild(msg.hwnd)) {
if (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST) {
Tooltip->RelayEvent(msg);
}
}
}
// Always let event go through.
//
return TWindow::PreProcessMsg(msg);
}
//
// Create Tooltips for GadgetWindow
//
void
TGadgetWindow::EvCreateTooltips()
{
// If 'WantTooltip' is enabled, created the control
//
if (WantTooltip)
EnableTooltip(true);
// Inform each gadget that the gadgetwindow is now created. This allows
// gadgets to perform initialization [eg. registering with the tooltip] which
// requires the 'HWND' to be valid
//
for (TGadget* g = Gadgets; g; g = g->NextGadget())
g->Created();
}
//
/// Intercepts window size changes to make sure that this gadget window follows its
/// own sizing rules. Also gives it a chance to layout wide-as-possible gadgets.
//
bool
TGadgetWindow::EvWindowPosChanging(WINDOWPOS & windowPos)
{
TWindow::EvWindowPosChanging(windowPos);
// Only process if it is a size change
//
if (!(windowPos.flags&SWP_NOSIZE))
{
// Knowing how big we might be, tile the gadgets to let them get positioned
// the way they want.
//
Attr.W = windowPos.cx;
Attr.H = windowPos.cy;
// Only tile if either we are dirty or have some wide-as-possible gadgets
// to adjust. Dont call LayoutSession() in order to avoid a loop. Derived
// versions might trigger a resize in there.
//
if (DirtyLayout || WideAsPossible)
InvalidateRect(TileGadgets());
// Find out how big we really want to be if any shrink wrap, and enforce it
//
UseDesiredSize();
if (ShrinkWrapWidth)
windowPos.cx = Attr.W;
if (ShrinkWrapHeight)
windowPos.cy = Attr.H;
// Redraw all if we're using themes (could have background gradients).
//
if (IsBackgroundThemed())
Invalidate(true);
}
return false;
}
//
/// Called by Paint to repaint all of the gadgets, PaintGadgets iterates through the
/// list of gadgets, determines the gadget's area, and repaints each gadget.
///
/// You can override this function to implement a specific look (for example,
/// separator line, raised, and so on).
//
void
TGadgetWindow::PaintGadgets(TDC& dc, bool, TRect& rect)
{
TPoint viewport = dc.GetViewportOrg();
for (TGadget* gadget = Gadgets; gadget; gadget = gadget->Next) {
if (gadget->GetBounds().Touches(rect)) {
dc.SetViewportOrg((TPoint&)gadget->GetBounds());
// If the gadget is a sloppy painter & needs clipping support, set the
// clip rect before painting, otherwise just paint. Most gadgets don't
// need this help
//
if (gadget->Clip) {
const auto savedClipRgn = dc.GetClipRgn();
dc.IntersectClipRect(gadget->GetBounds());
gadget->Paint(dc);
dc.SelectClipRgn(savedClipRgn);
}
else
gadget->Paint(dc);
}
}
dc.SetViewportOrg(viewport);
}
bool
TGadgetWindow::EvEraseBkgnd(HDC dc)
{
if (IsBackgroundThemed())
{
TThemePart part(GetHandle(), (LPCWSTR) L"REBAR", 0, 0);
const TRect r = GetClientRect();
part.DrawBackground(dc, r, r);
return true;
}
else
return TWindow::EvEraseBkgnd(dc);
}
//
/// Puts the font into the device context and calls PaintGadgets.
///
/// Respond to virtual TWindow Paint call. Call our own virtual PaintGadgets
/// to actually perform the painting of the gadgets
//
void
TGadgetWindow::Paint(TDC& dc, bool erase, TRect& rect)
{
dc.SelectObject(*Font);
PaintGadgets(dc, erase, rect);
#if defined(__TRACE) || defined(__WARN)
// Highlight the tools of this gadgetwindow for debugging purposes.
// Especially useful for docking windows which undergo internal state
// changes
//
TProfile iniFile(_T("Diagnostics"), _T(OWL_INI));
const auto useDiagColor = iniFile.GetInt(_T("DiagToolbarBorders")) != 0;
if (useDiagColor && Tooltip && Tooltip->IsWindow())
for (auto t : Tooltip->GetTools())
dc.FrameRect(t.GetBoundingBox(), TColor::LtRed);
#endif
}
//
/// Responds to a left button-down mouse message by forwarding the event to the
/// gadget (if it is enabled) positioned under the mouse.
//
void
TGadgetWindow::EvLButtonDown(uint modKeys, const TPoint& point)
{
TGadget* gadget = Capture ? Capture : GadgetFromPoint(point);
if (gadget && (gadget->GetEnabled() || Capture)) {
TPoint gadgetPoint = point - *(TSize*)&gadget->GetBounds().TopLeft();
gadget->LButtonDown(modKeys, gadgetPoint);
}
TWindow::EvLButtonDown(modKeys, point);
}
//
/// Responds to a left button-up mouse message by forwarding the event to the gadget
/// that has the capture or to the gadget positioned under the mouse
//
void
TGadgetWindow::EvLButtonUp(uint modKeys, const TPoint& point)
{
TGadget* gadget = Capture ? Capture : GadgetFromPoint(point);
if (gadget && (gadget->GetEnabled() || Capture)){
TPoint p(point.x - gadget->GetBounds().left, point.y - gadget->GetBounds().top);
gadget->LButtonUp(modKeys, p);
}
TWindow::EvLButtonUp(modKeys, point);
}
//
/// Passes double clicks through as if they were just a second click; finishes the
/// first click, and begins the second: Dn + Dbl + Up -> Dn + Up+Dn + Up.
//
void
TGadgetWindow::EvLButtonDblClk(uint modKeys, const TPoint& point)
{
EvLButtonUp(modKeys, point);
EvLButtonDown(modKeys, point);
}
//
/// Pass RButton messages to the gadget at the mouse location or the one
/// with Capture
///
/// \note The default right-mouse down handler of TGadget does
/// *NOT* set capture
//
void
TGadgetWindow::EvRButtonDown(uint modKeys, const TPoint& point)
{
TGadget* gadget = Capture ? Capture : GadgetFromPoint(point);
if (gadget && (gadget->GetEnabled() || Capture)) {
TPoint gadgetPoint = point - *(TSize*)&gadget->GetBounds().TopLeft();
gadget->RButtonDown(modKeys, gadgetPoint);
}
TWindow::EvRButtonDown(modKeys, point);
}
//
/// Pass RButton messages to the gadget at the mouse location or the one with
/// capture.
///
/// \note The default right-mouse down handler of TGadget does
/// *NOT* set capture
//
void
TGadgetWindow::EvRButtonUp(uint modKeys, const TPoint& point)
{
TGadget* gadget = Capture ? Capture : GadgetFromPoint(point);
if (gadget && (gadget->GetEnabled() || Capture)){
TPoint p(point.x - gadget->GetBounds().left, point.y - gadget->GetBounds().top);
gadget->RButtonUp(modKeys, p);
}
TWindow::EvRButtonUp(modKeys, point);
}
//
/// Forward mouse moves to the gadget that has captured the mouse, if any. Otherwise
/// checks for mouse entering and leaving gadgets. This could be enhanced by
/// delaying mouse enter messages until the mouse has been in the same area for a
/// while, or by looking ahead in the queue for mouse messages.
//
void
TGadgetWindow::EvMouseMove(uint modKeys, const TPoint& point)
{
struct Local
{
typedef void (TGadget::*MouseFun)(uint, const TPoint&);
static void Notify(TGadget* g, MouseFun f, uint modKeys, TPoint p)
{
if (!g) return;
p -= g->GetBounds().TopLeft(); // Convert to local coo.
(g->*f)(modKeys, p);
}
};
if (Capture)
Local::Notify(Capture, &TGadget::MouseMove, modKeys, point);
else
{
TGadget* gadget = GadgetFromPoint(point);
if (gadget != AtMouse)
{
Local::Notify(AtMouse, &TGadget::MouseLeave, modKeys, point);
AtMouse = gadget;
Local::Notify(AtMouse, &TGadget::MouseEnter, modKeys, point);
}
}
TWindow::EvMouseMove(modKeys, point);
}
//
//
//
void
TGadgetWindow::EnableTooltip(bool enable)
{
if (!Tooltip) {
// Find a parent for the tooltip: It's attractive to make the
// gadgetwindow the owner of the tooltip, a popup window. However, this
// will typically fail since the gadget window is invariably a child
// window. Windows seems to accomodate this situation by simply making
// the tooltip's owner the gadgetwindow's owner. This works fine until
// the owner of the gadgetwindow is destroyed before the gadgetwindow
// is destroyed, such as in the case of a gadgetwindow initally created
// as a floating docking toolbar; When it's docked, the floating slip,
// it's original owner, is destroyed and the gadget window is reparented
// to an edge slip. In this scenario, the tooltip is silently destroyed
// along with the floating slip [it's real owner!] and the docked
// gadgetwindow no longer provide tool tips!
//
// 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;
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);
}
}
//
/// Sets the Tooltip data member equal to tooltip. If the tooltip is invalid (if
/// Tooltip->GetHandle() fails), a new tooltip is created by the TTooltip::Create()
/// function.
//
void
TGadgetWindow::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(GetHandle() && !Tooltip->GetHandle()) {
// Make sure tooltip is disabled so it does not take input focus
Tooltip->ModifyStyle(0,WS_DISABLED);
Tooltip->Create();
}
}
}
//
/// When the gadget window receives a WM_COMMAND message, it is likely from a gadget
/// or control within a TControlGadget. This reroutes it to the command target.
//
TResult
TGadgetWindow::EvCommand(uint id, HWND hWndCtl, uint notifyCode)
{
TRACEX(OwlCmd, 1, "TGadgetWindow::EvCommand - id(" << id << "), ctl("
<< static_cast<void*>(hWndCtl) << "), code(" << notifyCode << ")");
// First allow any derived class that wants to handle the command
// NOTE: This search only caters for menu-style WM_COMMANDs (not those
// sent by controls)
//
TEventInfo eventInfo(0, id);
if (Find(eventInfo)) {
Dispatch(eventInfo, id);
return 0;
}
#if 0
// Prior versions of TGadgetWindow relied on TWindow's EvCommand for
// dispatching WM_COMMAND events. This required that one derives from
// a decoration class (eg. TControlbar, TToolbox) to handle control
// notifications. The current version uses a more generalized logic
// involving the CommandTarget and a frame ancestor class. This allows
// a client window to handle notifications of a control in a toolbar
// without using a TControlbar-derived class.
// However, if you need to previous behaviour, simply invoke TWindow's
// EvCommand from this handler.
return TWindow::EvCommand(id, hWndCtl, notifyCode);
#endif
TWindow* target;
TFrameWindow* frame;
// Find the frame who is our latest ancestor and make it our command target
//
for (target = GetParentO(); target; target = target->GetParentO()) {
frame = TYPESAFE_DOWNCAST(target, TFrameWindow);
if (frame || !target->GetParentO())
break;
}
// Make sure the frame doesn't think we are its command target, or a BAD
// loop will happen
//
if (target && (!frame || frame->GetCommandTarget() != GetHandle())) {
CHECK(target->IsWindow());
return target->EvCommand(id, hWndCtl, notifyCode);
}
// If all command routing fails, go back to basic dispatching of TWindow
//
return TWindow::EvCommand(id, hWndCtl, notifyCode);
}
//
/// When the gadget window receives a WM_COMMAND_ENABLE message, it is likely from a
/// gadget or control within a TControlGadget. This reroutes it to the command
/// target.
//
void
TGadgetWindow::EvCommandEnable(TCommandEnabler& ce)
{
// If someone derived from TGadgetWindow and handles the command there,
// give these handlers the first crack.
//
TEventInfo eventInfo(WM_COMMAND_ENABLE, ce.GetId());
if (Find(eventInfo)) {
Dispatch(eventInfo, 0, TParam2(&ce));
return;
}
TWindow* target = GetParentO();
// Forward to command target if the enabler was really destined for us, and
// not a routing from the frame.
//
if (target && ce.IsReceiver(*this)) {
CHECK(target->IsWindow());
ce.SetReceiver(*target);
target->EvCommandEnable(ce);
if( ce.GetHandled() )
return;
}
// Default to TWindow's implementation if the above routing fails
//
TWindow::EvCommandEnable(ce);
}
//----------------------------------------------------------------------------
//
//
/// Constructs a control out of a gadget.
//
TGadgetControl::TGadgetControl(TWindow* parent,
TGadget* soleGadget,
TFont* font,
TModule* module)
:
TGadgetWindow(parent, Horizontal, font, module)
{
// Let Attr rect override
//
SetShrinkWrap(false, false);
// Let inner gadget paint everything
//
TMargins mar(TMargins::Pixels, 0, 0, 0, 0);
SetMargins(mar);
if (soleGadget)
Insert(*soleGadget);
}
} // OWL namespace
/* ========================================================================== */
↑ V547 Expression 'contRow' is always false.
↑ V1027 Pointer to an object of the 'TRect' class is cast to unrelated 'TPoint' class.
↑ V1027 Pointer to an object of the 'TPoint' class is cast to unrelated 'TSize' class.
↑ V1027 Pointer to an object of the 'TPoint' class is cast to unrelated 'TSize' class.
↑ V807 Decreased performance. Consider creating a reference to avoid using the 'layout.GadgetBounds[i]' expression repeatedly.
↑ V807 Decreased performance. Consider creating a reference to avoid using the 'layout.GadgetBounds[j]' expression repeatedly.