Last active
October 19, 2023 18:25
-
-
Save pjwhams/6ebc040db3ab55615eafd831e184e39c to your computer and use it in GitHub Desktop.
Dump size and position of Qt QWidgets and QLayouts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2016-2023 Paul Walmsley and others | |
// Permission is hereby granted, free of charge, to any person obtaining | |
// a copy of this software and associated documentation files (the | |
// "Software"), to deal in the Software without restriction, including | |
// without limitation the rights to use, copy, modify, merge, publish, | |
// distribute, sublicense, and/or sell copies of the Software, and to | |
// permit persons to whom the Software is furnished to do so, subject to | |
// the following conditions: | |
// The above copyright notice and this permission notice shall be | |
// included in all copies or substantial portions of the Software. | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
#include <QtWidgets/QLayout> | |
#include <QtWidgets/QWidget> | |
#include <iostream> | |
#include <sstream> | |
#include <stdio.h> | |
#include <string> | |
#if _MSC_VER | |
#define snprintf _snprintf | |
#endif | |
std::string toString(const QSizePolicy::Policy& policy) | |
{ | |
switch (policy) | |
{ | |
case QSizePolicy::Fixed: | |
return "Fixed"; | |
case QSizePolicy::Minimum: | |
return "Minimum"; | |
case QSizePolicy::Maximum: | |
return "Maximum"; | |
case QSizePolicy::Preferred: | |
return "Preferred"; | |
case QSizePolicy::MinimumExpanding: | |
return "MinimumExpanding"; | |
case QSizePolicy::Expanding: | |
return "Expanding"; | |
case QSizePolicy::Ignored: | |
return "Ignored"; | |
} | |
return "unknown"; | |
} | |
std::string toString(const QSizePolicy& policy) | |
{ | |
return "(" + toString(policy.horizontalPolicy()) + ", " + toString(policy.verticalPolicy()) + ")"; | |
} | |
std::string toString(QLayout::SizeConstraint constraint) | |
{ | |
switch (constraint) | |
{ | |
case QLayout::SetDefaultConstraint: | |
return "SetDefaultConstraint"; | |
case QLayout::SetNoConstraint: | |
return "SetNoConstraint"; | |
case QLayout::SetMinimumSize: | |
return "SetMinimumSize"; | |
case QLayout::SetFixedSize: | |
return "SetFixedSize"; | |
case QLayout::SetMaximumSize: | |
return "SetMaximumSize"; | |
case QLayout::SetMinAndMaxSize: | |
return "SetMinAndMaxSize"; | |
} | |
return "unknown"; | |
} | |
std::string getWidgetInfo(const QWidget& w) | |
{ | |
const QRect& geom = w.geometry(); | |
QSize hint = w.sizeHint(); | |
char buf[1024]; | |
snprintf(buf, | |
1023, | |
"%s %p ('%s'), pos (%d, %d), size (%d x %d), hint (%d x %d) pol: %s %s\n", | |
w.metaObject()->className(), | |
(void*)&w, | |
w.objectName().toStdString().c_str(), | |
geom.x(), | |
geom.y(), | |
geom.width(), | |
geom.height(), | |
hint.width(), | |
hint.height(), | |
toString(w.sizePolicy()).c_str(), | |
(w.isVisible() ? "" : "**HIDDEN**")); | |
return buf; | |
} | |
std::string getLayoutItemInfo(QLayoutItem* item) | |
{ | |
if (dynamic_cast<QWidgetItem*>(item)) | |
{ | |
QWidgetItem* wi = dynamic_cast<QWidgetItem*>(item); | |
if (wi->widget()) | |
{ | |
return getWidgetInfo(*wi->widget()); | |
} | |
} | |
else if (dynamic_cast<QSpacerItem*>(item)) | |
{ | |
QSpacerItem* si = dynamic_cast<QSpacerItem*>(item); | |
QLayout* layout = si->layout(); | |
QSize hint = si->sizeHint(); | |
char buf[1024]; | |
snprintf(buf, | |
1023, | |
" SpacerItem hint (%d x %d) policy: %s constraint: %s\n", | |
hint.width(), | |
hint.height(), | |
toString(si->sizePolicy()).c_str(), | |
layout == nullptr ? "<null>" : toString(si->layout()->sizeConstraint()).c_str()); | |
return buf; | |
} | |
return ""; | |
} | |
//------------------------------------------------------------------------ | |
void dumpWidgetAndChildren(std::ostream& os, const QWidget* w, int level) | |
{ | |
std::string padding(""); | |
for (int i = 0; i <= level; i++) | |
padding += " "; | |
QLayout* layout = w->layout(); | |
QList<QWidget*> dumpedChildren; | |
if (layout && layout->isEmpty() == false) | |
{ | |
os << padding << "Layout "; | |
QMargins margins = layout->contentsMargins(); | |
os << " margin: (" << margins.left() << "," << margins.top() << "," << margins.right() << "," | |
<< margins.bottom() << "), constraint: " << toString(layout->sizeConstraint()); | |
if (dynamic_cast<QBoxLayout*>(layout)) | |
{ | |
QBoxLayout* boxLayout = dynamic_cast<QBoxLayout*>(layout); | |
os << " spacing: " << boxLayout->spacing(); | |
} | |
os << ":\n"; | |
int numItems = layout->count(); | |
for (int i = 0; i < numItems; i++) | |
{ | |
QLayoutItem* layoutItem = layout->itemAt(i); | |
std::string itemInfo = getLayoutItemInfo(layoutItem); | |
os << padding << " " << itemInfo; | |
QWidgetItem* wi = dynamic_cast<QWidgetItem*>(layoutItem); | |
if (wi && wi->widget()) | |
{ | |
dumpWidgetAndChildren(os, wi->widget(), level + 1); | |
dumpedChildren.push_back(wi->widget()); | |
} | |
} | |
} | |
// now output any child widgets that weren't dumped as part of the layout | |
QList<QWidget*> widgets = w->findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly); | |
QList<QWidget*> undumpedChildren; | |
foreach (QWidget* child, widgets) | |
{ | |
if (dumpedChildren.indexOf(child) == -1) | |
{ | |
undumpedChildren.push_back(child); | |
} | |
} | |
if (undumpedChildren.empty() == false) | |
{ | |
os << padding << " non-layout children:\n"; | |
foreach (QWidget* child, undumpedChildren) | |
{ | |
dumpWidgetAndChildren(os, child, level + 1); | |
} | |
} | |
} | |
//------------------------------------------------------------------------ | |
void dumpWidgetHierarchy(const QWidget* w) | |
{ | |
std::ostringstream oss; | |
oss << getWidgetInfo(*w); | |
dumpWidgetAndChildren(oss, w, 0); | |
std::cout << oss.str(); | |
} |
Hi @pjwhams, this is super useful! I did make the change that @oldmud0 described as I ran into the same issue
std::string getLayoutItemInfo(QLayoutItem* item) {
if (dynamic_cast<QWidgetItem*>(item)) {
QWidgetItem* wi = dynamic_cast<QWidgetItem*>(item);
if (wi->widget()) {
return getWidgetInfo(*wi->widget());
}
} else if (dynamic_cast<QSpacerItem*>(item)) {
QSpacerItem* si = dynamic_cast<QSpacerItem*>(item);
QLayout* layout = si->layout();
QSize hint = si->sizeHint();
char buf[1024];
snprintf(
buf,
1023,
" SpacerItem hint (%d x %d) policy: %s constraint: %s\n",
hint.width(),
hint.height(),
toString(si->sizePolicy()).c_str(),
layout == nullptr ? "<null>" : toString(si->layout()->sizeConstraint()).c_str());
return buf;
}
return "";
}
Do you mind attaching a license to the gist in a block comment at the top? (preferably MIT or Apache 2.0)
Done! I've incorporated your change too -- thanks for that
PySide6 version:
# Copyright (c) 2016-2023 Paul Walmsley and others
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QSizePolicy, QWidget, QLayoutItem, QWidgetItem, QSpacerItem, QBoxLayout
def _size_policy_to_string(policy: QSizePolicy):
return f'({policy.horizontalPolicy().name}, {policy.verticalPolicy().name})'
def _get_widget_info(w: QWidget) -> str:
geom = w.geometry()
hint = w.sizeHint()
hidden_str = "" if w.isVisible() else " **HIDDEN**"
return (f"{w.metaObject().className()} {w.winId()} ({w.objectName()}), "
f"pos ({geom.x()}, {geom.y()}), size ({geom.width()} x {geom.height()}), "
f"hint ({hint.width()} x {hint.height()}) policy: {_size_policy_to_string(w.sizePolicy())}{hidden_str}")
def _get_spacer_item_info(si: QSpacerItem):
layout = si.layout()
hint = si.sizeHint()
return (f" SpacerItem hint ({hint.width()} x {hint.height()}) "
f"policy: {_size_policy_to_string(si.sizePolicy())} "
f"constraint: {si.layout().sizeConstraint().name}")
def _get_layout_item_info(item: QLayoutItem):
if isinstance(item, QWidgetItem):
return _get_widget_info(item.widget())
elif isinstance(item, QSpacerItem):
return _get_spacer_item_info(item)
else:
return ""
def _print_widget_and_children(w: QWidget, level: int = 1):
padding = ""
for _ in range(level):
padding += " "
layout = w.layout()
dumped_children = []
if layout is not None and not layout.isEmpty():
margins = layout.contentsMargins()
margins_str = f"margin({margins.left()},{margins.top()},{margins.right()},{margins.bottom()})"
if isinstance(layout, QBoxLayout):
spacing_str = f" spacing: {layout.spacing()}"
else:
spacing_str = ""
print(f"{padding}Layout {margins_str}, constraint: {layout.sizeConstraint().name}{spacing_str}")
num_items = layout.count()
for i in range(num_items):
layout_item = layout.itemAt(i)
item_info = _get_layout_item_info(layout_item)
print(f"{padding}{item_info}")
if isinstance(layout_item, QWidgetItem):
_print_widget_and_children(layout_item.widget(), level + 1)
dumped_children.append(layout_item.widget())
# Now output any child widgets that weren't dumped as part of the layout
widgets = w.findChildren(QWidget, "", Qt.FindChildOption.FindDirectChildrenOnly)
undumped_children = []
for child in widgets:
if child not in dumped_children:
undumped_children.append(child)
if len(undumped_children) != 0:
print(f"{padding} non-layout children:")
for child in undumped_children:
_print_widget_and_children(child)
def print_widget_hierarchy(w: QWidget):
print(_get_widget_info(w))
_print_widget_and_children(w)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This code can crash if a QSpacerItem has no associated layout (see
getLayoutItemInfo
). Need to check if it does before callingsizeConstraint
. Otherwise, very useful.