/*
* Windows Dynamic Dialog Builder framework
* By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
*/
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include <cstdint>
#include "windowsh_wrapper.h"
#include "DialogBuilder.h"
#include "XSFCommon.h"
// Code modified from the following answer on Stack Overflow:
// http://stackoverflow.com/a/3407254
static inline std::uint32_t getNextMultipleOf4(std::uint32_t origNum)
{
std::uint32_t remainder = origNum % 4;
if (!remainder)
return origNum;
return origNum + 4 - remainder;
}
Point<short> RelativePosition::CalculatePosition(const Rect<short> &child, const Rect<short> &other)
{
Point<short> newPosition = child.position;
if (this->relativePosition.y != -1)
{
if (this->positionType == PositionType::FromTop || this->positionType == PositionType::FromTopLeft || this->positionType == PositionType::FromTopRight)
newPosition.y = other.position.y + this->relativePosition.y;
if (this->positionType == PositionType::FromBottom || this->positionType == PositionType::FromBottomLeft || this->positionType == PositionType::FromBottomRight)
newPosition.y = other.position.y + other.size.height + this->relativePosition.y;
}
if (this->relativePosition.x != -1)
{
if (this->positionType == PositionType::FromLeft || this->positionType == PositionType::FromTopLeft || this->positionType == PositionType::FromBottomLeft)
newPosition.x = other.position.x + this->relativePosition.x;
if (this->positionType == PositionType::FromRight || this->positionType == PositionType::FromTopRight || this->positionType == PositionType::FromBottomRight)
newPosition.x = other.position.x + other.size.width + this->relativePosition.x;
}
return newPosition;
}
void DialogTemplate::DialogGroup::CalculatePositions(bool doRightAndBottom)
{
short x = 0, num = this->controls.empty() ? 0 : static_cast<short>(this->controls.size());
for (; x < num; ++x)
{
auto &control = this->controls[x];
if (control->relativePosition)
{
bool valid = true;
if (!doRightAndBottom && control->relativePosition->type == RelativePosition::BaseType::ToParent)
{
switch (control->relativePosition->positionType)
{
case RelativePosition::PositionType::FromBottom:
case RelativePosition::PositionType::FromBottomLeft:
case RelativePosition::PositionType::FromBottomRight:
case RelativePosition::PositionType::FromRight:
case RelativePosition::PositionType::FromTopRight:
valid = false;
break;
default:
break;
}
}
if (valid)
{
Rect<short> other = this->rect;
if (control->relativePosition->type == RelativePosition::BaseType::ToSibling)
{
short siblingsBack = dynamic_cast<const RelativePositionToSibling *>(control->relativePosition.get())->siblingsBack;
if (x - siblingsBack >= 0)
other = this->controls[x - siblingsBack]->rect;
}
control->rect.position = control->relativePosition->CalculatePosition(control->rect, other);
}
}
}
}
void DialogTemplate::DialogGroup::CalculateSize()
{
short maxX = 0, maxY = 0, maxOtherWidth = 0, maxOtherHeight = 0;
std::for_each(this->controls.begin(), this->controls.end(), [&](const std::unique_ptr<DialogControl> &control)
{
bool usePosition = true;
if (control->relativePosition)
{
if (control->relativePosition->type == RelativePosition::BaseType::ToParent)
{
switch (control->relativePosition->positionType)
{
case RelativePosition::PositionType::FromBottom:
case RelativePosition::PositionType::FromBottomLeft:
case RelativePosition::PositionType::FromBottomRight:
case RelativePosition::PositionType::FromRight:
case RelativePosition::PositionType::FromTopRight:
usePosition = false;
/*if (control->relativePosition->relativePosition.x != -1 && control->rect.size.width + control->relativePosition->relativePosition.x > maxOtherWidth)
maxOtherWidth = control->rect.size.width + control->relativePosition->relativePosition.x;
if (control->relativePosition->relativePosition.y != -1 && control->GetControlHeight() + control->relativePosition->relativePosition.y > maxOtherHeight)
maxOtherHeight = control->GetControlHeight() + control->relativePosition->relativePosition.y;*/
break;
default:
break;
}
}
}
if (usePosition)
{
if (control->rect.position.x + control->rect.size.width > maxX)
maxX = control->rect.position.x + control->rect.size.width;
if (control->rect.position.y + control->GetControlHeight() > maxY)
maxY = control->rect.position.y + control->GetControlHeight();
}
});
this->rect.size.width = (maxX - this->rect.position.x) + maxOtherWidth + 4;
this->rect.size.height = (maxY - this->rect.position.y) + maxOtherHeight + 7;
}
std::uint16_t DialogTemplate::DialogGroup::GetControlCount() const
{
std::uint16_t count = 1;
std::for_each(this->controls.begin(), this->controls.end(), [&](const std::unique_ptr<DialogControl> &control) { count += control->GetControlCount(); });
return count;
}
std::vector<std::uint8_t> DialogTemplate::DialogGroup::GenerateControlTemplate() const
{
auto data = std::vector<std::uint8_t>(getNextMultipleOf4(24 + sizeof(wchar_t) * (this->groupName.length() + 1)), 0);
*reinterpret_cast<std::uint32_t *>(&data[0]) = WS_CHILD | WS_VISIBLE | BS_GROUPBOX | this->style;
*reinterpret_cast<std::uint32_t *>(&data[4]) = this->exstyle;
*reinterpret_cast<std::uint16_t *>(&data[8]) = this->rect.position.x;
*reinterpret_cast<std::uint16_t *>(&data[10]) = this->rect.position.y;
*reinterpret_cast<std::uint16_t *>(&data[12]) = this->rect.size.width;
*reinterpret_cast<std::uint16_t *>(&data[14]) = this->rect.size.height;
*reinterpret_cast<std::uint16_t *>(&data[16]) = static_cast<std::uint16_t>(this->id);
*reinterpret_cast<std::uint16_t *>(&data[18]) = 0xFFFF;
*reinterpret_cast<std::uint16_t *>(&data[20]) = 0x0080;
CopyToString(this->groupName, reinterpret_cast<wchar_t *>(&data[22]));
std::for_each(this->controls.begin(), this->controls.end(), [&](const std::unique_ptr<DialogControl> &control)
{
auto controlData = control->GenerateControlTemplate();
data.insert(data.end(), controlData.begin(), controlData.end());
});
return data;
}
std::vector<std::uint8_t> DialogTemplate::DialogControlWithoutLabel::GenerateControlTemplate() const
{
auto data = std::vector<std::uint8_t>(getNextMultipleOf4(24 + sizeof(wchar_t)), 0);
*reinterpret_cast<std::uint32_t *>(&data[0]) = WS_CHILD | WS_VISIBLE | this->style;
*reinterpret_cast<std::uint32_t *>(&data[4]) = this->exstyle;
*reinterpret_cast<std::uint16_t *>(&data[8]) = this->rect.position.x;
*reinterpret_cast<std::uint16_t *>(&data[10]) = this->rect.position.y;
*reinterpret_cast<std::uint16_t *>(&data[12]) = this->rect.size.width;
*reinterpret_cast<std::uint16_t *>(&data[14]) = this->rect.size.height;
*reinterpret_cast<std::uint16_t *>(&data[16]) = static_cast<uint16_t>(this->id);
*reinterpret_cast<std::uint16_t *>(&data[18]) = 0xFFFF;
*reinterpret_cast<std::uint16_t *>(&data[20]) = this->type;
return data;
}
std::vector<std::uint8_t> DialogTemplate::DialogControlWithLabel::GenerateControlTemplate() const
{
auto data = std::vector<std::uint8_t>(getNextMultipleOf4(24 + sizeof(wchar_t) * (this->label.length() + 1)), 0);
*reinterpret_cast<std::uint32_t *>(&data[0]) = WS_CHILD | WS_VISIBLE | this->style;
*reinterpret_cast<std::uint32_t *>(&data[4]) = this->exstyle;
*reinterpret_cast<std::uint16_t *>(&data[8]) = this->rect.position.x;
*reinterpret_cast<std::uint16_t *>(&data[10]) = this->rect.position.y;
*reinterpret_cast<std::uint16_t *>(&data[12]) = this->rect.size.width;
*reinterpret_cast<std::uint16_t *>(&data[14]) = this->rect.size.height;
*reinterpret_cast<std::uint16_t *>(&data[16]) = static_cast<std::uint16_t>(this->id);
*reinterpret_cast<std::uint16_t *>(&data[18]) = 0xFFFF;
*reinterpret_cast<std::uint16_t *>(&data[20]) = this->type;
CopyToString(this->label, reinterpret_cast<wchar_t *>(&data[22]));
return data;
}
std::uint16_t DialogTemplate::GetTotalControlCount() const
{
std::uint16_t count = 0;
std::for_each(this->controls.begin(), this->controls.end(), [&](const std::unique_ptr<DialogControl> &control) { count += control->GetControlCount(); });
return count;
}
void DialogTemplate::AddGroupControl(const DialogControlBuilder<DialogGroupBuilder> &builder)
{
auto newGroup = DialogGroup::CreateControl(builder);
if (builder.index == -1)
this->controls.push_back(std::move(newGroup));
else
this->controls.insert(this->controls.begin() + builder.index, std::move(newGroup));
}
void DialogTemplate::AddEditBoxControl(const DialogControlBuilder<DialogEditBoxBuilder> &builder)
{
this->AddControlToGroup(DialogEditBox::CreateControl(builder), builder);
}
void DialogTemplate::AddLabelControl(const DialogControlBuilder<DialogLabelBuilder> &builder)
{
this->AddControlToGroup(DialogLabel::CreateControl(builder), builder);
}
void DialogTemplate::AddCheckBoxControl(const DialogControlBuilder<DialogCheckBoxBuilder> &builder)
{
this->AddControlToGroup(DialogButton::CreateControl(builder), builder);
}
void DialogTemplate::AddButtonControl(const DialogControlBuilder<DialogButtonBuilder> &builder)
{
this->AddControlToGroup(DialogButton::CreateControl(builder), builder);
}
void DialogTemplate::AddListBoxControl(const DialogControlBuilder<DialogListBoxBuilder> &builder)
{
this->AddControlToGroup(DialogListBox::CreateControl(builder), builder);
}
void DialogTemplate::AddComboBoxControl(const DialogControlBuilder<DialogComboBoxBuilder> &builder)
{
this->AddControlToGroup(DialogComboBox::CreateControl(builder), builder);
}
bool DialogTemplate::CalculateControlPosition(short index, bool doRightAndBottom)
{
auto &control = this->controls[index];
bool valid = true;
if (control->relativePosition)
{
if (!doRightAndBottom && control->relativePosition->type == RelativePosition::BaseType::ToParent)
{
switch (control->relativePosition->positionType)
{
case RelativePosition::PositionType::FromBottom:
case RelativePosition::PositionType::FromBottomLeft:
case RelativePosition::PositionType::FromBottomRight:
case RelativePosition::PositionType::FromRight:
case RelativePosition::PositionType::FromTopRight:
valid = false;
break;
default:
break;
}
}
if (valid)
{
Rect<short> other = Rect<short>(Point<short>(), this->size);
if (control->relativePosition->type == RelativePosition::BaseType::ToSibling)
{
short siblingsBack = dynamic_cast<const RelativePositionToSibling *>(control->relativePosition.get())->siblingsBack;
if (index - siblingsBack >= 0)
other = this->controls[index - siblingsBack]->rect;
}
control->rect.position = control->relativePosition->CalculatePosition(control->rect, other);
}
}
return valid;
}
void DialogTemplate::CalculateSize()
{
short maxX = 0, maxY = 0, maxOtherWidth = 0, maxOtherHeight = 0;
std::for_each(this->controls.begin(), this->controls.end(), [&](const std::unique_ptr<DialogControl> &control)
{
bool usePosition = true;
if (control->relativePosition)
{
if (control->relativePosition->type == RelativePosition::BaseType::ToParent)
{
switch (control->relativePosition->positionType)
{
case RelativePosition::PositionType::FromBottom:
case RelativePosition::PositionType::FromBottomLeft:
case RelativePosition::PositionType::FromBottomRight:
case RelativePosition::PositionType::FromRight:
case RelativePosition::PositionType::FromTopRight:
usePosition = false;
/*if (control->relativePosition->relativePosition.x != -1 && control->rect.size.width + control->relativePosition->relativePosition.x > maxOtherWidth)
maxOtherWidth = control->rect.size.width + control->relativePosition->relativePosition.x;
if (control->relativePosition->relativePosition.y != -1 && control->GetControlHeight() + control->relativePosition->relativePosition.y > maxOtherHeight)
maxOtherHeight = control->GetControlHeight() + control->relativePosition->relativePosition.y;*/
break;
default:
break;
}
}
}
if (usePosition)
{
if (control->rect.position.x + control->rect.size.width > maxX)
maxX = control->rect.position.x + control->rect.size.width;
if (control->rect.position.y + control->GetControlHeight() > maxY)
maxY = control->rect.position.y + control->GetControlHeight();
}
});
this->size.width = maxX + maxOtherWidth + 7;
this->size.height = maxY + maxOtherHeight + 7;
}
void DialogTemplate::AutoSize()
{
// Step 1: Calculate positions of dialog controls (only if the controls's position is relative to a sibling or if it's relativeness to the parent is from the top, left, or top left)
short x = 0, num = this->controls.empty() ? 0 : static_cast<short>(this->controls.size()), maxGroupWidth = 0;
for (; x < num; ++x)
{
auto &control = this->controls[x];
bool valid = this->CalculateControlPosition(x, false);
if (valid && control->controlType == DialogControlType::Group)
{
dynamic_cast<DialogGroup *>(control.get())->CalculatePositions(false);
// Technically step 2, but calculate the size of the group
dynamic_cast<DialogGroup *>(control.get())->CalculateSize();
if (control->rect.size.width > maxGroupWidth)
maxGroupWidth = control->rect.size.width;
}
}
// Step 2: Resize all group controls to be the same width, and then within the group, recalculate positions for all controls
for (x = 0; x < num; ++x)
{
auto &control = this->controls[x];
if (control->controlType != DialogControlType::Group)
continue;
control->rect.size.width = maxGroupWidth;
dynamic_cast<DialogGroup *>(control.get())->CalculatePositions(true);
}
// Step 3: Resize the dialog box itself
this->CalculateSize();
// Step 4: Calculate position of all dialog controls
for (x = 0; x < num; ++x)
this->CalculateControlPosition(x, true);
}
const DLGTEMPLATE *DialogTemplate::GenerateTemplate()
{
this->templateData.clear();
std::uint16_t controlCount = this->GetTotalControlCount();
this->templateData.resize(getNextMultipleOf4(24 + sizeof(wchar_t) * (this->title.length() + 1 + (this->fontName.empty() ? 0 : this->fontName.length() + 1))), 0);
*reinterpret_cast<std::uint32_t *>(&this->templateData[0]) = this->style | (this->fontName.empty() ? 0 : DS_SETFONT);
*reinterpret_cast<std::uint32_t *>(&this->templateData[4]) = this->exstyle;
*reinterpret_cast<std::uint16_t *>(&this->templateData[8]) = controlCount;
*reinterpret_cast<std::uint16_t *>(&this->templateData[14]) = this->size.width;
*reinterpret_cast<std::uint16_t *>(&this->templateData[16]) = this->size.height;
CopyToString(this->title, reinterpret_cast<wchar_t *>(&this->templateData[22]));
if (!this->fontName.empty())
{
*reinterpret_cast<std::uint16_t *>(&this->templateData[22 + sizeof(wchar_t) * (this->title.length() + 1)]) = this->fontSizeInPts;
CopyToString(this->fontName, reinterpret_cast<wchar_t *>(&this->templateData[24 + sizeof(wchar_t) * (this->title.length() + 1)]));
}
std::for_each(this->controls.begin(), this->controls.end(), [&](const std::unique_ptr<DialogControl> &control)
{
auto controlData = control->GenerateControlTemplate();
this->templateData.insert(this->templateData.end(), controlData.begin(), controlData.end());
});
return reinterpret_cast<const DLGTEMPLATE *>(&this->templateData[0]);
}