Skip to content

Instantly share code, notes, and snippets.

@pjwhams
Last active October 19, 2023 18:25
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save pjwhams/6ebc040db3ab55615eafd831e184e39c to your computer and use it in GitHub Desktop.
Dump size and position of Qt QWidgets and QLayouts
// 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();
}
@oldmud0
Copy link

oldmud0 commented Dec 21, 2020

This code can crash if a QSpacerItem has no associated layout (see getLayoutItemInfo). Need to check if it does before calling sizeConstraint. Otherwise, very useful.

@percentcer
Copy link

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)

@pjwhams
Copy link
Author

pjwhams commented Aug 16, 2023

Done! I've incorporated your change too -- thanks for that

@dfraska-ftl
Copy link

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