Commit 7169b3ac authored by Amish Naidu's avatar Amish Naidu
Browse files

Sublime: Fix crash when changing areas

Summary:
Fixes regression introduced in D15450 by making IdealButtonBarLayout derive from
QBoxLayout instead of QLayout and thus delete most operations to Qt instead
of manually handling them, removing the bugged implementation.

Added minimumSizeHint in IdealToolButton to prevent it from being resized
to zero.

IdealButtonBarLayout is now a child layout for vertical bars as well,
the top level layout is stretched and allows for context menu.

BUG: 399025

Reviewers: #kdevelop, kossebau, rjvbb

Reviewed By: #kdevelop, kossebau, rjvbb

Subscribers: kossebau, rjvbb, kdevelop-devel

Tags: #kdevelop

Differential Revision: https://phabricator.kde.org/D15625
parent 167b4a1c
......@@ -110,12 +110,12 @@ IdealButtonBarWidget::IdealButtonBarWidget(Qt::DockWidgetArea area,
setContextMenuPolicy(Qt::CustomContextMenu);
setToolTip(i18nc("@info:tooltip", "Right click to add new tool views."));
m_buttonsLayout = new IdealButtonBarLayout(orientation(), this);
if (area == Qt::BottomDockWidgetArea)
{
QBoxLayout *statusLayout = new QBoxLayout(QBoxLayout::LeftToRight, this);
statusLayout->setMargin(0);
m_buttonsLayout = new IdealButtonBarLayout(orientation());
statusLayout->addLayout(m_buttonsLayout);
statusLayout->addStretch(1);
......@@ -125,9 +125,13 @@ IdealButtonBarWidget::IdealButtonBarWidget(Qt::DockWidgetArea area,
cornerLayout->setMargin(0);
cornerLayout->setSpacing(0);
statusLayout->addWidget(m_corner);
} else {
QBoxLayout *superLayout = new QBoxLayout(QBoxLayout::TopToBottom, this);
superLayout->setMargin(0);
superLayout->addLayout(m_buttonsLayout);
superLayout->addStretch(1);
}
else
m_buttonsLayout = new IdealButtonBarLayout(orientation(), this);
}
QAction* IdealButtonBarWidget::addWidget(IdealDockWidget *dock,
......
......@@ -24,42 +24,37 @@
#include <QStyle>
#include <QWidget>
#include <QEvent>
#include <numeric>
using namespace Sublime;
IdealButtonBarLayout::IdealButtonBarLayout(Qt::Orientation orientation, QWidget *parent)
: QLayout(parent)
, _orientation(orientation)
, _height(0)
namespace
{
setContentsMargins(0, 0, 0, 0);
invalidate();
}
void IdealButtonBarLayout::invalidate()
{
m_minSizeDirty = true;
m_sizeHintDirty = true;
m_layoutDirty = true;
QLayout::invalidate();
}
QBoxLayout::Direction toDirection(Qt::Orientation orientation)
{
return orientation == Qt::Horizontal ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom;
}
IdealButtonBarLayout::~IdealButtonBarLayout()
{
qDeleteAll(_items);
}
void IdealButtonBarLayout::setHeight(int height)
IdealButtonBarLayout::IdealButtonBarLayout(Qt::Orientation orientation, QWidget* styleParent)
: QBoxLayout(toDirection(orientation)) // creating a child layout, styleParent is only saved for style
, m_styleParentWidget(styleParent)
, _orientation(orientation)
{
Q_ASSERT(orientation() == Qt::Vertical);
_height = height;
if (m_styleParentWidget) {
m_styleParentWidget->installEventFilter(this);
}
(void) invalidate();
setContentsMargins(0, 0, 0, 0);
setSpacing(buttonSpacing());
}
IdealButtonBarLayout::~IdealButtonBarLayout() = default;
Qt::Orientation IdealButtonBarLayout::orientation() const
{
return _orientation;
......@@ -70,237 +65,19 @@ Qt::Orientations IdealButtonBarLayout::expandingDirections() const
return orientation();
}
QSize IdealButtonBarLayout::minimumSize() const
{
// The code below appears to be completely wrong --
// it will return the maximum size of a single button, not any
// estimate as to how much space is necessary to draw all buttons
// in a minimally acceptable way.
if (m_minSizeDirty) {
if (orientation() == Qt::Vertical) {
const int width = doVerticalLayout(QRect(0, 0, 0, _height), false);
return QSize(width, 0);
}
m_min = QSize(0, 0);
for (QLayoutItem* item : _items) {
m_min = m_min.expandedTo(item->minimumSize());
}
m_minSizeDirty = false;
}
return m_min;
}
QSize IdealButtonBarLayout::sizeHint() const
{
if (m_sizeHintDirty) {
const int buttonSpacing = this->buttonSpacing();
int orientationSize = 0;
int crossSize = 0;
bool first = true;
for (QLayoutItem *item : _items) {
QSize hint = item->sizeHint();
int orientationSizeHere;
int crossSizeHere;
if (orientation() == Qt::Vertical)
{
orientationSizeHere = hint.height();
crossSizeHere = hint.width();
}
else
{
orientationSizeHere = hint.width();
crossSizeHere = hint.height();
}
if (first)
{
crossSize = crossSizeHere;
}
else
{
orientationSize += buttonSpacing;
}
orientationSize += orientationSizeHere;
first = false;
}
if (orientation() == Qt::Vertical)
m_hint = QSize(crossSize, orientationSize);
else
m_hint = QSize(orientationSize, crossSize);
if (!_items.empty())
{
/* If we have no items, just use (0, 0) as hint, don't
append any margins. */
int l, t, r, b;
getContentsMargins(&l, &t, &r, &b);
m_hint += QSize(l+r, t+b);
}
m_sizeHintDirty = false;
}
return m_hint;
}
void IdealButtonBarLayout::setGeometry(const QRect &rect)
{
if (m_layoutDirty || rect != geometry()) {
if (orientation() == Qt::Vertical)
doVerticalLayout(rect);
else
doHorizontalLayout(rect);
}
}
void IdealButtonBarLayout::addItem(QLayoutItem *item)
{
_items.append(item);
invalidate();
}
QLayoutItem* IdealButtonBarLayout::itemAt(int index) const
{
return _items.value(index, nullptr);
}
QLayoutItem* IdealButtonBarLayout::takeAt(int index)
{
if (index >= 0 && index < _items.count())
return _items.takeAt(index);
invalidate();
return nullptr;
}
int IdealButtonBarLayout::count() const
{
return _items.count();
}
int IdealButtonBarLayout::buttonSpacing() const
{
auto pw = parentWidget();
return pw ? pw->style()->pixelMetric(QStyle::PM_ToolBarItemSpacing) : 0;
}
int IdealButtonBarLayout::doVerticalLayout(const QRect &rect, bool updateGeometry) const
{
const int buttonSpacing = this->buttonSpacing();
int l, t, r, b;
getContentsMargins(&l, &t, &r, &b);
int x = rect.x() + l;
int y = rect.y() + t;
int currentLineWidth = 0;
if (_items.empty()) {
return x + currentLineWidth + r;
}
const bool shrink = rect.height() < sizeHint().height();
const int maximumHeight = rect.height() / _items.size();
int shrinkedHeight = -1;
if (shrink) {
int smallItemCount = 0;
const int surplus = std::accumulate(_items.begin(), _items.end(), 0, [maximumHeight, &smallItemCount](int acc, QLayoutItem* item) {
const int itemHeight = item->sizeHint().height();
if (itemHeight <= maximumHeight) {
acc += maximumHeight - itemHeight;
++smallItemCount;
}
return acc;
});
Q_ASSERT(_items.size() != smallItemCount); // should be true since rect.width != sizeHint.width
// evenly distribute surplus height over large items
shrinkedHeight = maximumHeight + surplus / (_items.size() - smallItemCount);
if (!m_styleParentWidget) {
return 0;
}
for (QLayoutItem* item : _items) {
const QSize itemSizeHint = item->sizeHint();
const int itemWidth = itemSizeHint.width();
int itemHeight = itemSizeHint.height();
if (shrink && itemSizeHint.height() > maximumHeight) {
itemHeight = shrinkedHeight;
}
if (updateGeometry) {
item->setGeometry(QRect(x, y, itemWidth, itemHeight));
}
currentLineWidth = qMax(currentLineWidth, itemWidth);
y += itemHeight + buttonSpacing;
}
m_layoutDirty = updateGeometry;
return x + currentLineWidth + r;
return m_styleParentWidget->style()->pixelMetric(QStyle::PM_ToolBarItemSpacing);
}
int IdealButtonBarLayout::doHorizontalLayout(const QRect &rect, bool updateGeometry) const
bool IdealButtonBarLayout::eventFilter(QObject* watched, QEvent* event)
{
const int buttonSpacing = this->buttonSpacing();
int l, t, r, b;
getContentsMargins(&l, &t, &r, &b);
int x = rect.x() + l;
int y = rect.y() + t;
int currentLineHeight = 0;
if (_items.empty()) {
return y + currentLineHeight + b;
if (event->type() == QEvent::StyleChange) {
setSpacing(buttonSpacing());
}
const bool shrink = rect.width() < sizeHint().width();
const int maximumWidth = rect.width() / _items.size();
int shrinkedWidth = -1;
if (shrink) {
int smallItemCount = 0;
const int surplus = std::accumulate(_items.begin(), _items.end(), 0, [maximumWidth, &smallItemCount](int acc, QLayoutItem* item) {
const int itemWidth = item->sizeHint().width();
if (itemWidth <= maximumWidth) {
acc += maximumWidth - itemWidth;
++smallItemCount;
}
return acc;
});
Q_ASSERT(_items.size() != smallItemCount); // should be true since rect.width != sizeHint.width
// evenly distribute surplus width on the large items
shrinkedWidth = maximumWidth + surplus / (_items.size() - smallItemCount);
}
for (QLayoutItem* item : _items) {
const QSize itemSizeHint = item->sizeHint();
int itemWidth = itemSizeHint.width();
const int itemHeight = itemSizeHint.height();
if (shrink && itemSizeHint.width() > maximumWidth) {
itemWidth = shrinkedWidth;
}
if (updateGeometry) {
item->setGeometry(QRect(x, y, itemWidth, itemHeight));
}
currentLineHeight = qMax(currentLineHeight, itemHeight);
x += itemWidth + buttonSpacing;
}
m_layoutDirty = updateGeometry;
return y + currentLineHeight + b;
return QBoxLayout::eventFilter(watched, event);
}
......@@ -22,59 +22,34 @@
#ifndef KDEVPLATFORM_SUBLIME_IDEALLAYOUT_H
#define KDEVPLATFORM_SUBLIME_IDEALLAYOUT_H
#include <QLayout>
#include <QBoxLayout>
#include "sublimedefs.h"
namespace Sublime {
class IdealButtonBarLayout: public QLayout
class IdealButtonBarLayout: public QBoxLayout
{
Q_OBJECT
public:
explicit IdealButtonBarLayout(Qt::Orientation orientation, QWidget *parent = nullptr);
IdealButtonBarLayout(Qt::Orientation orientation, QWidget* styleParent);
~IdealButtonBarLayout() override;
void setHeight(int height);
inline Qt::Orientation orientation() const;
Qt::Orientations expandingDirections() const override;
QSize minimumSize() const override;
QSize sizeHint() const override;
void setGeometry(const QRect &rect) override;
void addItem(QLayoutItem *item) override;
QLayoutItem* itemAt(int index) const override;
QLayoutItem* takeAt(int index) override;
int count() const override;
void invalidate() override;
protected:
int doVerticalLayout(const QRect &rect, bool updateGeometry = true) const;
int doHorizontalLayout(const QRect &rect, bool updateGeometry = true) const;
bool eventFilter(QObject* watched, QEvent* event) override;
int buttonSpacing() const;
private:
QList<QLayoutItem *> _items;
QWidget* const m_styleParentWidget;
const Qt::Orientation _orientation;
int _height;
mutable bool m_minSizeDirty : 1;
mutable bool m_sizeHintDirty : 1;
mutable bool m_layoutDirty : 1;
mutable QSize m_min;
mutable QSize m_hint;
};
}
......
......@@ -2,6 +2,7 @@
Copyright 2007 Roberto Raggi <roberto@kdevelop.org>
Copyright 2007 Hamish Rodda <rodda@kde.org>
Copyright 2011 Alexander Dymo <adymo@kdevelop.org>
Copyright 2018 Amish Naidu <amhndu@gmail.com>
Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
......@@ -24,6 +25,7 @@
#include <KAcceleratorManager>
#include <QStyleOption>
#include <QStylePainter>
#include <QApplication>
IdealToolButton::IdealToolButton(Qt::DockWidgetArea area, QWidget *parent)
: QToolButton(parent), _area(area)
......@@ -35,6 +37,14 @@ IdealToolButton::IdealToolButton(Qt::DockWidgetArea area, QWidget *parent)
setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
setContextMenuPolicy(Qt::CustomContextMenu);
QSizePolicy sizePolicy = this->sizePolicy();
if (orientation() == Qt::Horizontal) {
sizePolicy.setHorizontalPolicy(QSizePolicy::Maximum);
} else {
sizePolicy.setVerticalPolicy(QSizePolicy::Maximum);
}
setSizePolicy(sizePolicy);
}
Qt::Orientation IdealToolButton::orientation() const
......@@ -86,6 +96,36 @@ QSize IdealToolButton::sizeHint() const
}
}
QSize IdealToolButton::minimumSizeHint() const
{
ensurePolished();
QStyleOptionToolButton opt;
initStyleOption(&opt);
QSize minimumSize;
// if style has icons, minimumSize is the size of the icon
if (toolButtonStyle() != Qt::ToolButtonTextOnly && !opt.icon.isNull()) {
minimumSize = opt.iconSize;
if (_area == Qt::LeftDockWidgetArea || _area == Qt::RightDockWidgetArea) {
minimumSize.transpose();
}
} else {
// if no icon, set an arbitrary minimum size
QFontMetrics fm = fontMetrics();
minimumSize = fm.size(Qt::TextShowMnemonic, opt.text.left(4));
}
minimumSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, minimumSize, this).
expandedTo(QApplication::globalStrut());
if (_area == Qt::TopDockWidgetArea || _area == Qt::BottomDockWidgetArea) {
return minimumSize;
}
return minimumSize.transposed();
}
void IdealToolButton::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
......
......@@ -36,6 +36,8 @@ public:
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
protected:
void paintEvent(QPaintEvent *event) override;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment