maxLibQt
CollapsingToolBar.h
Go to the documentation of this file.
1 /*
2  CollapsingToolBar
3  https://github.com/mpaperno/maxLibQt
4 
5  COPYRIGHT: (c)2019 Maxim Paperno; All Right Reserved.
6  Contact: http://www.WorldDesign.com/contact
7 
8  LICENSE:
9 
10  Commercial License Usage
11  Licensees holding valid commercial licenses may use this file in
12  accordance with the terms contained in a written agreement between
13  you and the copyright holder.
14 
15  GNU General Public License Usage
16  Alternatively, this file may be used under the terms of the GNU
17  General Public License as published by the Free Software Foundation,
18  either version 3 of the License, or (at your option) any later version.
19 
20  This program is distributed in the hope that it will be useful,
21  but WITHOUT ANY WARRANTY; without even the implied warranty of
22  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23  GNU General Public License for more details.
24 
25  A copy of the GNU General Public License is available at <http://www.gnu.org/licenses/>.
26 */
27 
28 #ifndef COLLAPSINGTOOLBAR_H_
29 #define COLLAPSINGTOOLBAR_H_
30 
31 #include <QToolBar>
32 #include <QToolButton>
33 #include <QAction>
34 #include <QActionEvent>
35 #include <QStyle>
36 
89 {
90  Q_OBJECT
91  public:
93 
94  explicit CollapsingToolBar(const QString &title, QWidget *parent = nullptr) :
95  QToolBar(title, parent)
96  {
97  initSizes();
98  // If icon sizes change we need to recalculate all the size hints, but we need to wait until the buttons have adjusted themselves, so we queue the update.
99  connect(this, &QToolBar::iconSizeChanged, [this](const QSize &) {
100  QMetaObject::invokeMethod(this, "recalcExpandedSize", Qt::QueuedConnection);
101  });
102  // The drag handle can mess up our sizing, update preferred size if it changes.
103  connect(this, &QToolBar::movableChanged, [this](bool movable) {
104  const int handleSz = style()->pixelMetric(QStyle::PM_ToolBarHandleExtent, nullptr, this);;
105  m_expandedSize = (movable ? m_expandedSize + handleSz : m_expandedSize - handleSz);
106  adjustForSize();
107  });
108  }
109 
110  protected:
111 
112  // Monitor action events to keep track of required size.
113  void actionEvent(QActionEvent *e) override
114  {
116 
117  int width = 0;
118  switch (e->type())
119  {
120  case QEvent::ActionAdded:
121  // Personal pet-peeve... optionally set buttons with menus to have instant popups instead of splits with the main button doing nothing.
122  //if (QToolButton *tb = qobject_cast<QToolButton *>(widgetForAction(e->action())))
123  // tb->setPopupMode(QToolButton::InstantPopup);
124  //Q_FALLTHROUGH;
126  width = widthForAction(e->action());
127  if (width <= 0)
128  return;
129 
130  if (e->type() == QEvent::ActionAdded || !m_actionWidths.contains(e->action()))
131  m_expandedSize += width + m_spacing;
132  else
133  m_expandedSize = m_expandedSize - m_actionWidths.value(e->action()) + width;
134  m_actionWidths.insert(e->action(), width);
135  break;
136 
138  if (!m_actionWidths.contains(e->action()))
139  return;
140  width = m_actionWidths.value(e->action());
141  m_expandedSize -= width + m_spacing;
142  m_actionWidths.remove(e->action());
143  break;
144 
145  default:
146  return;
147  }
148  adjustForSize();
149  }
150 
151  bool event(QEvent *e) override
152  {
153  // Watch for style change
154  if (e->type() == QEvent::StyleChange)
155  recalcExpandedSize();
156  return QToolBar::event(e);
157  }
158 
159  void resizeEvent(QResizeEvent *e) override
160  {
161  adjustForSize();
163  }
164 
165  private slots:
166  // Here we do the actual switching of tool button style based on available width.
167  void adjustForSize()
168  {
169  int availableWidth = contentsRect().width();
170  if (!isVisible() || m_expandedSize <= 0 || availableWidth <= 0)
171  return;
172 
173  switch (toolButtonStyle()) {
175  if (availableWidth > m_expandedSize)
177  break;
178 
180  if (availableWidth <= m_expandedSize)
182  break;
183 
184  default:
185  break;
186  }
187  }
188 
189  // Loops over all previously-added actions and re-calculates new size (eg. after icon size change)
190  void recalcExpandedSize()
191  {
192  if (m_actionWidths.isEmpty())
193  return;
194  initSizes();
195  int width = 0;
196  QHash<QAction *, int>::iterator it = m_actionWidths.begin();
197  for ( ; it != m_actionWidths.end(); ++it) {
198  width = widthForAction(it.key());
199  if (width <= 0)
200  continue;
201  m_expandedSize += width + m_spacing;
202  it.value() = width;
203  }
204  adjustForSize();
205  }
206 
207  private:
208  void initSizes()
209  {
210  // Preload some sizes based on style settings.
211  // This is the spacing between items
212  m_spacing = style()->pixelMetric(QStyle::PM_ToolBarItemSpacing, nullptr, this);
213  // Size of a separator
214  m_separatorWidth = style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, nullptr, this);
215  // The layout margins (we can't even get the private QToolBarLayout via layout() so we figure it out like it does)
216  m_expandedSize = (style()->pixelMetric(QStyle::PM_ToolBarItemMargin, nullptr, this) + style()->pixelMetric(QStyle::PM_ToolBarFrameWidth, nullptr, this)) * 2;
217  // And the size of the drag handle if we have one
218  if (isMovable())
219  m_expandedSize += style()->pixelMetric(QStyle::PM_ToolBarHandleExtent, nullptr, this);
220  }
221 
222  int widthForAction(QAction *action) const
223  {
224  // Try to find how wide the action representation (widget/separator) is.
225  if (action->isSeparator())
226  return m_separatorWidth;
227 
228  if (QToolButton *tb = qobject_cast<QToolButton *>(widgetForAction(action))) {
229  const Qt::ToolButtonStyle oldStyle = tb->toolButtonStyle();
230  // force the widest size
231  tb->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
232  const int width = tb->sizeHint().width();
233  tb->setToolButtonStyle(oldStyle);
234  return width;
235  }
236 
237  if (const QWidget *w = widgetForAction(action))
238  return w->sizeHint().width();
239 
240  return 0;
241  }
242 
243  int m_expandedSize = -1; // The maximum size we need with all buttons expanded and allowing for margins/etc
244  int m_spacing = 0; // Layout spacing between items
245  int m_separatorWidth = 0; // Width of separators
246  QHash<QAction *, int> m_actionWidths; // Use this to track action additions/removals/changes
247 };
248 
249 #endif // COLLAPSINGTOOLBAR_H_
QEvent::Type type() const const
QHash::iterator insert(const Key &key, const T &value)
CollapsingToolBar(QWidget *parent=nullptr)
const Key key(const T &value) const const
QRect contentsRect() const const
void iconSizeChanged(const QSize &iconSize)
QStyle * style() const const
virtual int pixelMetric(QStyle::PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
bool isVisible() const const
Qt::ToolButtonStyle toolButtonStyle() const const
QAction * action() const const
int width() const const
bool isSeparator() const const
QWidget * widgetForAction(QAction *action) const const
QHash::iterator begin()
ToolButtonIconOnly
bool event(QEvent *e) override
void actionEvent(QActionEvent *e) override
int remove(const Key &key)
const T value(const Key &key) const const
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
PM_ToolBarHandleExtent
int width() const const
bool isEmpty() const const
virtual bool event(QEvent *event) override
CollapsingToolBar(const QString &title, QWidget *parent=nullptr)
bool contains(const Key &key) const const
QHash::iterator end()
virtual void resizeEvent(QResizeEvent *event)
QueuedConnection
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void movableChanged(bool movable)
QObject * parent() const const
void resizeEvent(QResizeEvent *e) override
The CollapsingToolBar will change the QToolButton::toolButtonStyle of all added QToolButtons (via QAc...
virtual void actionEvent(QActionEvent *event) override