├── images ├── smile.png ├── user.png ├── setting.png ├── reopen-folder.svg ├── folder.svg ├── right_arrow.svg └── right_arrow_down.svg ├── main.cpp ├── src.qrc ├── widget.h ├── style.qss ├── README.md ├── LICENSE ├── widget.ui ├── AdvancedToolBox.pro ├── widget.cpp ├── advancedtoolbox.h └── advancedtoolbox.cpp /images/smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiilpux17/AdvancedToolBox/HEAD/images/smile.png -------------------------------------------------------------------------------- /images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiilpux17/AdvancedToolBox/HEAD/images/user.png -------------------------------------------------------------------------------- /images/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eiilpux17/AdvancedToolBox/HEAD/images/setting.png -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | //QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 7 | QApplication a(argc, argv); 8 | a.setStyleSheet("file:///:/style.qss"); 9 | Widget w; 10 | w.resize(400, 600); 11 | w.show(); 12 | return a.exec(); 13 | } 14 | -------------------------------------------------------------------------------- /src.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | style.qss 4 | images/folder.svg 5 | images/reopen-folder.svg 6 | images/right_arrow.svg 7 | images/right_arrow_down.svg 8 | images/setting.png 9 | images/smile.png 10 | images/user.png 11 | 12 | 13 | -------------------------------------------------------------------------------- /widget.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_H 2 | #define WIDGET_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class Widget; 8 | } 9 | 10 | class AdvancedToolBox; 11 | class Widget : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit Widget(QWidget *parent = nullptr); 17 | ~Widget(); 18 | 19 | private: 20 | Ui::Widget *ui; 21 | 22 | AdvancedToolBox * toolBox = nullptr; 23 | }; 24 | 25 | #endif // WIDGET_H 26 | -------------------------------------------------------------------------------- /images/reopen-folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /style.qss: -------------------------------------------------------------------------------- 1 | AdvancedToolBox::separator{ 2 | width:1px; 3 | background: red; 4 | } 5 | 6 | AdvancedToolBox::tab{ 7 | padding: 10px; 8 | border: 0px solid; 9 | background: #ADADAD; 10 | color: white; 11 | } 12 | 13 | AdvancedToolBox::tab:hover{ 14 | color: red; 15 | } 16 | 17 | AdvancedToolBox::branch:closed{ 18 | image: url(:/images/right_arrow.svg); 19 | width: 30px; 20 | padding: 5px; 21 | } 22 | 23 | AdvancedToolBox::branch{ 24 | image: url(:/images/right_arrow_down.svg); 25 | width: 30px; 26 | padding: 5px; 27 | } 28 | -------------------------------------------------------------------------------- /images/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /images/right_arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AdvancedToolBox 2 | 3 | 由于QToolBox不支持同时展开和折叠tab,功能比较弱。所以用Qt重新实现了一个更好的tool box,仅支持垂直布局。 4 | 5 | 6 | 7 | ### 支持特性: 8 | 9 | * 每个tab页支持展开和折叠 10 | 11 | * 可鼠标移动handle调整tab大小(类似QSplitter) 12 | 13 | * 可以拖拽tab标题重排tab 14 | 15 | * 可以通过style sheet设置tab标题、separator handle、expanding icon等样式 16 | 17 | ### 布局实现 18 | 19 | AdvancedToolBox内部使用手动布局,每个标签页区域有三个元素:separator、title、container。 20 | 21 | * separator,可以通过style sheet设置颜色等,可以通过鼠标拖拽调整相关tab的尺寸 22 | 23 | * title,主要绘制展开或折叠状态、图标、标题文字,点击可以折叠和展开,展开和折叠设置了动画 24 | 25 | * container,用户设置的Widget的容器,使用这层容器的目的是为了在展开或折叠时,避免过多的resize event。 26 | 27 | 考虑到需要拖拽排序,每个标签页区域没有使用独立布局,AdvancedToolBox窗口触发布局时,对每个标签页的三个元素按顺序计算高度并布局。 28 | 29 | 30 | ### 待支持功能 31 | 32 | - [ ] 增加展开和折叠时信号 33 | - [ ] 标签页标题右侧支持自定义QAction 34 | - [ ] 展开和折叠时,应该触发widget的show和hide事件 35 | -------------------------------------------------------------------------------- /images/right_arrow_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 eiilpux17 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Widget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Widget 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PushButton 23 | 24 | 25 | 26 | 27 | 28 | 29 | Qt::Horizontal 30 | 31 | 32 | 33 | 40 34 | 20 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /AdvancedToolBox.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2020-06-13T16:36:18 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = AdvancedToolBox 12 | TEMPLATE = app 13 | 14 | # The following define makes your compiler emit warnings if you use 15 | # any feature of Qt which has been marked as deprecated (the exact warnings 16 | # depend on your compiler). Please consult the documentation of the 17 | # deprecated API in order to know how to port your code away from it. 18 | DEFINES += QT_DEPRECATED_WARNINGS 19 | 20 | # You can also make your code fail to compile if you use deprecated APIs. 21 | # In order to do so, uncomment the following line. 22 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 23 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 24 | 25 | CONFIG += c++11 26 | 27 | SOURCES += \ 28 | main.cpp \ 29 | widget.cpp \ 30 | advancedtoolbox.cpp 31 | 32 | HEADERS += \ 33 | widget.h \ 34 | advancedtoolbox.h 35 | 36 | FORMS += \ 37 | widget.ui 38 | 39 | # Default rules for deployment. 40 | qnx: target.path = /tmp/$${TARGET}/bin 41 | else: unix:!android: target.path = /opt/$${TARGET}/bin 42 | !isEmpty(target.path): INSTALLS += target 43 | 44 | RESOURCES += \ 45 | src.qrc 46 | -------------------------------------------------------------------------------- /widget.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | #include "ui_widget.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "advancedtoolbox.h" 8 | 9 | Widget::Widget(QWidget *parent) : 10 | QWidget(parent), 11 | ui(new Ui::Widget) 12 | { 13 | ui->setupUi(this); 14 | 15 | QScrollArea * scroll = new QScrollArea(this); 16 | scroll->setWidgetResizable(true); 17 | toolBox = new AdvancedToolBox(this); 18 | QFrame * frame = new QFrame(toolBox); 19 | frame->setStyleSheet("QFrame{background:#E8BD92;}"); 20 | frame->setMaximumHeight(300); 21 | 22 | QIcon iconfolder(":/images/folder.svg"); 23 | iconfolder.addFile(":/images/reopen-folder.svg", QSize(), QIcon::Normal, QIcon::On); 24 | toolBox->addWidget(frame, "AAA",iconfolder); 25 | 26 | frame = new QFrame(toolBox); 27 | frame->setStyleSheet("QFrame{background:#BDE8A7;}"); 28 | frame->setMinimumHeight(200); 29 | frame->setMinimumWidth(500); 30 | toolBox->addWidget(frame, "BBB", QIcon(":/images/smile.png")); 31 | 32 | QWidget * btn = new QPushButton("abc", toolBox); 33 | btn->setStyleSheet("QFrame{background:#78B294;}"); 34 | toolBox->addWidget(btn, "CCC", QIcon(":/images/user.png")); 35 | 36 | frame = new QFrame(toolBox); 37 | frame->setMinimumHeight(200); 38 | frame->setStyleSheet("QFrame{background:#82C7D9;}"); 39 | toolBox->addWidget(frame, "DDD"); 40 | 41 | scroll->setWidget(toolBox); 42 | ui->verticalLayout->insertWidget(1, scroll, 1); 43 | } 44 | 45 | Widget::~Widget() 46 | { 47 | delete ui; 48 | } 49 | -------------------------------------------------------------------------------- /advancedtoolbox.h: -------------------------------------------------------------------------------- 1 | #ifndef ADVANCEDTOOLBOX_H 2 | #define ADVANCEDTOOLBOX_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class AdvancedToolBoxPrivate; 9 | class ToolBoxTitle; 10 | class ToolBoxSplitterHandle; 11 | 12 | class AdvancedToolBox : public QWidget 13 | { 14 | Q_OBJECT 15 | public: 16 | explicit AdvancedToolBox(QWidget *parent = nullptr); 17 | ~AdvancedToolBox(); 18 | 19 | QSize sizeHint() const; 20 | QSize minimumSizeHint() const; 21 | 22 | void addWidget(QWidget * widget, const QString & label, const QIcon & icon = QIcon()); 23 | int indexOf(QWidget * widget); 24 | QWidget * takeIndex(int index); 25 | QWidget * widget(int index); 26 | 27 | void setItemExpand(int index, bool expand = true); 28 | void setItemVisible(int index, bool visible = true); 29 | 30 | void setItemText(int index, const QString & text); 31 | void setItemIcon(int index, const QIcon & icon); 32 | QString itemText(int index); 33 | QIcon itemIcon(int index); 34 | 35 | int textIndentation(); 36 | void resetTextIndentation(int indent = -1); 37 | 38 | void setDragSortEnable(bool enable); 39 | void setAnimationEnable(bool enable); 40 | 41 | protected: 42 | bool event(QEvent *e); 43 | void paintEvent(QPaintEvent *event); 44 | void dragEnterEvent(QDragEnterEvent *event); 45 | void dragMoveEvent(QDragMoveEvent *event); 46 | void dropEvent(QDropEvent *event); 47 | void dragLeaveEvent(QDragLeaveEvent *event); 48 | void startDrag(int index, const QPoint & gpos); 49 | 50 | private: 51 | Q_DECLARE_PRIVATE(AdvancedToolBox) 52 | Q_DISABLE_COPY(AdvancedToolBox) 53 | QScopedPointer d_ptr; 54 | 55 | private: 56 | friend class ToolBoxTitle; 57 | friend class ToolBoxSplitterHandle; 58 | }; 59 | 60 | class ToolBoxSplitterHandle : public QWidget 61 | { 62 | public: 63 | ToolBoxSplitterHandle(AdvancedToolBox * parent); 64 | void setIndex(int index); 65 | int index(); 66 | protected: 67 | void mouseMoveEvent(QMouseEvent *) override; 68 | void mousePressEvent(QMouseEvent *) override; 69 | void mouseReleaseEvent(QMouseEvent *) override; 70 | 71 | private: 72 | int _index = -1; 73 | bool pressed = false; 74 | QPoint moveStart; 75 | }; 76 | 77 | #endif // ADVANCEDTOOLBOX_H 78 | -------------------------------------------------------------------------------- /advancedtoolbox.cpp: -------------------------------------------------------------------------------- 1 | #include "advancedtoolbox.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | class ToolBoxPageContainer : public QWidget 21 | { 22 | public: 23 | using QWidget::QWidget; 24 | bool event(QEvent *e) 25 | { 26 | if(e->type() == QEvent::LayoutRequest) 27 | updateGeometry(); 28 | return QWidget::event(e); 29 | } 30 | }; 31 | 32 | class ToolBoxTitle : public QAbstractButton 33 | { 34 | Q_OBJECT 35 | signals: 36 | void titleClicked(int index); 37 | void titleContextMenuRequest(int index, const QPoint &pos); 38 | 39 | public: 40 | explicit ToolBoxTitle(const QString &label, const QIcon &icon, AdvancedToolBox *parent); 41 | void setIndex(int index) 42 | { 43 | tabIndex = index; 44 | } 45 | QSize sizeHint() const; 46 | QSize minimumSizeHint() const 47 | { 48 | return sizeHint(); 49 | } 50 | void setExpanded(bool expanded) 51 | { 52 | if(this->expanded != expanded) 53 | { 54 | this->expanded = expanded; 55 | update(); 56 | } 57 | } 58 | 59 | protected: 60 | bool event(QEvent *e); 61 | void initStyleOption(QStyleOptionToolBox *option) const; 62 | void paintEvent(QPaintEvent *); 63 | void changeEvent(QEvent *e) 64 | { 65 | _sizeHint = QSize(); 66 | updateGeometry(); 67 | QWidget::changeEvent(e); 68 | } 69 | 70 | private: 71 | QPoint pressedPos; 72 | bool pressed = false; 73 | bool expanded = true; 74 | mutable QSize _sizeHint; 75 | bool hoverBranch = false; 76 | int tabIndex = -1; 77 | }; 78 | 79 | class AdvancedToolBoxPrivate : public QObject 80 | { 81 | 82 | Q_DECLARE_PUBLIC(AdvancedToolBox) 83 | public: 84 | class ToolBoxItem; 85 | 86 | public: 87 | AdvancedToolBoxPrivate(AdvancedToolBox *p) 88 | : QObject() 89 | , q_ptr(p) 90 | { 91 | QStyle *style = q_ptr->style(); 92 | handleWidth = style->pixelMetric(QStyle::PM_DockWidgetSeparatorExtent, nullptr, p); 93 | indent = style->pixelMetric(QStyle::PM_TreeViewIndentation, nullptr, p); 94 | } 95 | 96 | ~AdvancedToolBoxPrivate() 97 | { 98 | qDeleteAll(items); 99 | } 100 | 101 | QWidget *takeIndex(int index); 102 | QWidget *widget(int index); 103 | 104 | void setIndexExpand(int index, bool expand = true); 105 | void setIndexVisible(int index, bool visible = true); 106 | 107 | void styleChangedEvent(); 108 | 109 | void setIndentation(int i); 110 | 111 | void insertWidgetToList(int index, QWidget *widget, const QString &label, const QIcon &icon = QIcon()); 112 | void doLayout(); 113 | 114 | void expandStateChanged(int index, bool expand); 115 | void moveHandle(int index, int distance); 116 | void updateGeometries(bool animate = false); 117 | 118 | void resetPages(); 119 | ToolBoxSplitterHandle *createHandle(); 120 | ToolBoxTitle *createTitle(const QString &label, const QIcon &icon); 121 | void showTitleMenu(int index, const QPoint &pos); 122 | 123 | void setDragRubberVisible(bool visible, const QRect &rect = QRect()); 124 | void updateTitleIndent(); 125 | void resetManualSize(); 126 | void widgetDestroyed(QObject *o); 127 | void resetSizeHint(); 128 | 129 | protected: 130 | int indent = 10; 131 | int handleWidth = 5; 132 | QSize minSizeHint; 133 | QSize sizeHint; 134 | int boxSpacing = 0; 135 | QList items; 136 | 137 | QRubberBand *dragRubber = nullptr; 138 | 139 | bool isAnimationState = false; 140 | bool nextIsAnimation = false; 141 | bool dragSortEnable = true; 142 | bool animationEnable = true; 143 | 144 | AdvancedToolBox *q_ptr = nullptr; 145 | friend class AdvancedToolBox; 146 | friend class ToolBoxSplitterHandle; 147 | }; 148 | 149 | class AdvancedToolBoxPrivate::ToolBoxItem 150 | { 151 | QWidget *widget = nullptr; 152 | ToolBoxSplitterHandle *handle = nullptr; // 移动handle 153 | ToolBoxTitle *tabTitle = nullptr; // 标题栏文字、图标等 154 | QWidget *tabContainer = nullptr; // 容器,方便做折叠动画 155 | 156 | int layoutHeight = 0; // 实际布局高度 157 | QSize sizeHint; // 建议高度 158 | QSize minSize; // 最小高度 159 | QSize maxSize; // 最大高度 160 | int manualHeight = 0; // 手动高度,用于在调整高度或者展开折叠后做一次缓存,避免resize时抖动 161 | 162 | bool freezeTarget = false; 163 | bool isExpanded = true; 164 | inline bool expanded() { return isExpanded; } 165 | 166 | void calItemSize() 167 | { 168 | QWidgetItem wi(widget); 169 | sizeHint = wi.sizeHint(); 170 | minSize = wi.minimumSize(); 171 | maxSize = wi.maximumSize(); 172 | if(sizeHint.height() < 0) 173 | sizeHint.rheight() = 100; 174 | } 175 | 176 | int preferHeight() 177 | { 178 | int prefer = 0; 179 | if(manualHeight > 0) 180 | prefer = manualHeight; 181 | else if(sizeHint.height() > 0) 182 | prefer = sizeHint.height(); 183 | return qMin(qMax(minSize.height(), prefer), maxSize.height()); 184 | } 185 | 186 | inline bool canResize() const 187 | { 188 | return !widget->isHidden() && isExpanded; 189 | } 190 | 191 | inline bool isHidden() const 192 | { 193 | return widget->isHidden(); 194 | } 195 | 196 | inline QString title() const 197 | { 198 | return tabTitle->text(); 199 | } 200 | 201 | friend class AdvancedToolBox; 202 | friend class AdvancedToolBoxPrivate; 203 | }; 204 | 205 | using AdToolBoxItem = AdvancedToolBoxPrivate::ToolBoxItem; 206 | 207 | AdvancedToolBox::AdvancedToolBox(QWidget *parent) 208 | : QWidget(parent) 209 | , d_ptr(new AdvancedToolBoxPrivate(this)) 210 | { 211 | setAcceptDrops(true); 212 | } 213 | 214 | AdvancedToolBox::~AdvancedToolBox() 215 | { 216 | 217 | } 218 | 219 | QSize AdvancedToolBox::sizeHint() const 220 | { 221 | Q_D(const AdvancedToolBox); 222 | return d->sizeHint; 223 | } 224 | 225 | QSize AdvancedToolBox::minimumSizeHint() const 226 | { 227 | Q_D(const AdvancedToolBox); 228 | return d->minSizeHint; 229 | } 230 | 231 | void AdvancedToolBox::addWidget(QWidget *widget, const QString &label, const QIcon &icon) 232 | { 233 | Q_D(AdvancedToolBox); 234 | Q_ASSERT(widget); 235 | int n = d->items.count(); 236 | d->insertWidgetToList(n, widget, label, icon); 237 | } 238 | 239 | int AdvancedToolBox::indexOf(QWidget *widget) 240 | { 241 | Q_D(AdvancedToolBox); 242 | auto &items = d->items; 243 | for(int i = 0; i < items.size(); i++) 244 | { 245 | QWidget *w = items.at(i)->widget; 246 | if(w == widget) 247 | return i; 248 | } 249 | return -1; 250 | } 251 | 252 | QWidget *AdvancedToolBox::takeIndex(int index) 253 | { 254 | Q_D(AdvancedToolBox); 255 | return d->takeIndex(index); 256 | } 257 | 258 | QWidget *AdvancedToolBox::widget(int index) 259 | { 260 | Q_D(AdvancedToolBox); 261 | auto item = d->items.value(index); 262 | if(item) 263 | return item->widget; 264 | return nullptr; 265 | } 266 | 267 | void AdvancedToolBox::setItemExpand(int index, bool expand) 268 | { 269 | Q_D(AdvancedToolBox); 270 | d->setIndexExpand(index, expand); 271 | } 272 | 273 | void AdvancedToolBox::setItemVisible(int index, bool visible) 274 | { 275 | Q_D(AdvancedToolBox); 276 | d->setIndexVisible(index, visible); 277 | } 278 | 279 | void AdvancedToolBox::setItemText(int index, const QString &text) 280 | { 281 | Q_D(AdvancedToolBox); 282 | if(auto item = d->items.value(index)) 283 | item->tabTitle->setText(text); 284 | } 285 | 286 | void AdvancedToolBox::setItemIcon(int index, const QIcon &icon) 287 | { 288 | Q_D(AdvancedToolBox); 289 | if(auto item = d->items.value(index)) 290 | item->tabTitle->setIcon(icon); 291 | } 292 | 293 | QString AdvancedToolBox::itemText(int index) 294 | { 295 | Q_D(AdvancedToolBox); 296 | if(auto item = d->items.value(index)) 297 | return item->title(); 298 | return QString(); 299 | } 300 | 301 | QIcon AdvancedToolBox::itemIcon(int index) 302 | { 303 | Q_D(AdvancedToolBox); 304 | if(auto item = d->items.value(index)) 305 | return item->tabTitle->icon(); 306 | return QIcon(); 307 | } 308 | 309 | int AdvancedToolBox::textIndentation() 310 | { 311 | Q_D(AdvancedToolBox); 312 | return d->indent; 313 | } 314 | 315 | void AdvancedToolBox::resetTextIndentation(int indent) 316 | { 317 | Q_D(AdvancedToolBox); 318 | d->setIndentation(indent); 319 | } 320 | 321 | void AdvancedToolBox::setDragSortEnable(bool enable) 322 | { 323 | Q_D(AdvancedToolBox); 324 | d->dragSortEnable = enable; 325 | } 326 | 327 | void AdvancedToolBox::setAnimationEnable(bool enable) 328 | { 329 | Q_D(AdvancedToolBox); 330 | d->animationEnable = enable; 331 | } 332 | 333 | bool AdvancedToolBox::event(QEvent *e) 334 | { 335 | bool ret = QWidget::event(e); 336 | switch(e->type()) 337 | { 338 | case QEvent::LayoutRequest: 339 | { 340 | Q_D(AdvancedToolBox); 341 | for(auto item : d->items) 342 | item->calItemSize(); 343 | d->resetSizeHint(); 344 | d->doLayout(); 345 | } 346 | break; 347 | case QEvent::StyleChange: 348 | { 349 | Q_D(AdvancedToolBox); 350 | int old = d->handleWidth; 351 | d->styleChangedEvent(); 352 | d->updateTitleIndent(); 353 | if(old != d->handleWidth) 354 | { 355 | d->resetSizeHint(); 356 | d->doLayout(); 357 | } 358 | } 359 | break; 360 | case QEvent::Resize: 361 | { 362 | Q_D(AdvancedToolBox); 363 | d->doLayout(); 364 | } 365 | break; 366 | default: 367 | break; 368 | } 369 | return ret; 370 | } 371 | 372 | void AdvancedToolBox::paintEvent(QPaintEvent *event) 373 | { 374 | QWidget::paintEvent(event); 375 | 376 | Q_D(AdvancedToolBox); 377 | const int hw = d->handleWidth; 378 | QStyleOption opt(0); 379 | opt.state = QStyle::State_None; 380 | opt.state |= this->isEnabled() ? QStyle::State_Enabled : QStyle::State_None; 381 | opt.state |= QStyle::State_Horizontal; 382 | opt.palette = this->palette(); 383 | 384 | bool first = true; 385 | QPainter painter(this); 386 | for(auto item : d->items) 387 | { 388 | if(item->widget->isHidden()) 389 | continue; 390 | 391 | if(!first) 392 | { 393 | int top = item->tabTitle->pos().y(); 394 | opt.rect = QRect(0, top - hw, this->width(), hw); 395 | this->style()->drawPrimitive(QStyle::PE_IndicatorDockWidgetResizeHandle, &opt, &painter, this); 396 | } 397 | first = false; 398 | } 399 | } 400 | 401 | void AdvancedToolBox::dragEnterEvent(QDragEnterEvent *event) 402 | { 403 | const QMimeData *data = event->mimeData(); 404 | if(data->hasFormat("advanced-toolbox-drag-index") && event->source() == this) 405 | { 406 | event->acceptProposedAction(); 407 | return; 408 | } 409 | return QWidget::dragEnterEvent(event); 410 | } 411 | 412 | void AdvancedToolBox::dragMoveEvent(QDragMoveEvent *event) 413 | { 414 | Q_D(AdvancedToolBox); 415 | 416 | const QMimeData *data = event->mimeData(); 417 | bool ok = false; 418 | int drag_index = data->data("advanced-toolbox-drag-index").toInt(&ok); 419 | if(!ok) 420 | { 421 | event->ignore(); 422 | return; 423 | } 424 | 425 | auto &items = d->items; 426 | const int y = event->pos().y(); 427 | int hover = -1; 428 | QRect rubber_rect; 429 | for(int i = 0; i < items.count(); i++) 430 | { 431 | auto *item = items.at(i); 432 | if(item->isHidden()) 433 | continue; 434 | if(item->expanded()) 435 | { 436 | QRect cr = item->tabContainer->geometry(); 437 | if(cr.bottom() >= y) 438 | { 439 | hover = i; 440 | QRect tr = item->tabTitle->geometry(); 441 | int mid = (tr.top() + cr.bottom() + 1) / 2; 442 | int top = mid < y ? mid : tr.top(); 443 | rubber_rect = QRect(tr.x(), top, tr.width(), 0); 444 | rubber_rect.setBottom(mid < y ? cr.bottom() : mid); 445 | break; 446 | } 447 | } 448 | else 449 | { 450 | QRect r = item->tabTitle->geometry(); 451 | if(r.bottom() >= y) 452 | { 453 | hover = i; 454 | int mid = r.center().y(); 455 | int top = mid < y ? r.bottom() + 1 : r.top() - d->handleWidth; 456 | rubber_rect = QRect(r.x(), top, r.width(), d->handleWidth); 457 | if(d->handleWidth <= 1) 458 | rubber_rect.adjust(0, -2, 0, 2); 459 | break; 460 | } 461 | } 462 | } 463 | 464 | if(hover >= 0 && hover != drag_index) 465 | { 466 | event->acceptProposedAction(); 467 | d->setDragRubberVisible(true, rubber_rect); 468 | } 469 | else 470 | { 471 | d->setDragRubberVisible(false); 472 | event->ignore(); 473 | } 474 | } 475 | 476 | void AdvancedToolBox::dropEvent(QDropEvent *event) 477 | { 478 | Q_D(AdvancedToolBox); 479 | d->setDragRubberVisible(false); 480 | const QMimeData *data = event->mimeData(); 481 | bool ok = false; 482 | int drag_index = data->data("advanced-toolbox-drag-index").toInt(&ok); 483 | if(!ok) 484 | { 485 | event->ignore(); 486 | return; 487 | } 488 | 489 | auto &items = d->items; 490 | const int y = event->pos().y(); 491 | int target = -1; 492 | for(int i = 0; i < items.count(); i++) 493 | { 494 | auto *item = items.at(i); 495 | if(!item->isHidden()) 496 | { 497 | if(item->expanded()) 498 | { 499 | QRect cr = item->tabContainer->geometry(); 500 | if(cr.bottom() >= y) 501 | { 502 | QRect tr = item->tabTitle->geometry(); 503 | int mid = (tr.top() + cr.bottom() + 1) / 2; 504 | target = i + (mid < y ? 1 : 0); 505 | target -= target > drag_index ? 1 : 0; 506 | break; 507 | } 508 | } 509 | else 510 | { 511 | QRect r = item->tabTitle->geometry(); 512 | if(r.bottom() >= y) 513 | { 514 | target = i + (r.center().y() < y ? 1 : 0); 515 | target -= target > drag_index ? 1 : 0; 516 | break; 517 | } 518 | } 519 | } 520 | } 521 | 522 | if(target >= 0 && target != drag_index) 523 | { 524 | event->acceptProposedAction(); 525 | d->items.move(drag_index, target); 526 | d->resetPages(); 527 | d->updateGeometries(); 528 | } 529 | else 530 | { 531 | event->ignore(); 532 | } 533 | } 534 | 535 | void AdvancedToolBox::dragLeaveEvent(QDragLeaveEvent *event) 536 | { 537 | Q_D(AdvancedToolBox); 538 | d->setDragRubberVisible(false); 539 | event->ignore(); 540 | } 541 | 542 | void AdvancedToolBox::startDrag(int index, const QPoint &gpos) 543 | { 544 | Q_D(AdvancedToolBox); 545 | if(d->dragSortEnable && index >= 0) 546 | { 547 | QDrag drag(this); 548 | QMimeData *data = new QMimeData(); 549 | data->setData("advanced-toolbox-drag-index", QByteArray::number(index)); 550 | drag.setMimeData(data); 551 | 552 | Q_D(AdvancedToolBox); 553 | auto item = d->items.at(index); 554 | QPixmap pix = item->tabTitle->grab(); 555 | QPoint pos = item->tabTitle->mapFromGlobal(gpos); 556 | drag.setHotSpot(pos); 557 | drag.setPixmap(pix); 558 | 559 | drag.exec(Qt::MoveAction); 560 | } 561 | } 562 | 563 | QWidget *AdvancedToolBoxPrivate::takeIndex(int index) 564 | { 565 | Q_Q(AdvancedToolBox); 566 | ToolBoxItem *item = items.value(index); 567 | if(item) 568 | { 569 | QWidget *ret = item->widget; 570 | if(ret) 571 | { 572 | ret->setVisible(false); 573 | ret->setParent(q); 574 | } 575 | item->tabTitle->deleteLater(); 576 | item->tabContainer->deleteLater(); 577 | item->handle->deleteLater(); 578 | delete item; 579 | doLayout(); 580 | return ret; 581 | } 582 | return nullptr; 583 | } 584 | 585 | void AdvancedToolBoxPrivate::setIndexExpand(int index, bool expand) 586 | { 587 | auto item = items.value(index); 588 | if(item && item->expanded() != expand) 589 | { 590 | item->isExpanded = expand; 591 | expandStateChanged(index, expand); 592 | } 593 | } 594 | 595 | void AdvancedToolBoxPrivate::setIndexVisible(int index, bool visible) 596 | { 597 | auto item = items.value(index); 598 | if(!item) 599 | return; 600 | 601 | if((item->widget->isHidden() && !visible) || !item->widget->isHidden() && visible) 602 | return; 603 | 604 | item->widget->setVisible(visible); 605 | item->tabTitle->setVisible(visible); 606 | item->tabContainer->setVisible(visible); 607 | 608 | if(!visible && item->isExpanded) 609 | item->manualHeight = item->layoutHeight; 610 | 611 | resetSizeHint(); 612 | 613 | if(visible) 614 | resetManualSize(); 615 | 616 | // 这里选择重新布局,可以考虑尽可能维持该页面后布局,提升体验 617 | doLayout(); 618 | } 619 | 620 | void AdvancedToolBoxPrivate::styleChangedEvent() 621 | { 622 | QStyle *style = q_ptr->style(); 623 | handleWidth = style->pixelMetric(QStyle::PM_DockWidgetSeparatorExtent, nullptr, q_ptr); 624 | indent = style->pixelMetric(QStyle::PM_TreeViewIndentation, nullptr, q_ptr); 625 | } 626 | 627 | void AdvancedToolBoxPrivate::setIndentation(int i) 628 | { 629 | int old = indent; 630 | if(i < 0) 631 | indent = q_ptr->style()->pixelMetric(QStyle::PM_TreeViewIndentation, nullptr, q_ptr); 632 | else 633 | indent = i; 634 | if(old != indent) 635 | { 636 | updateTitleIndent(); 637 | } 638 | } 639 | 640 | void AdvancedToolBoxPrivate::insertWidgetToList(int index, QWidget *widget, const QString &label, const QIcon &icon) 641 | { 642 | Q_Q(AdvancedToolBox); 643 | int count = items.count(); 644 | if(index < 0 || index > count) 645 | index = count; 646 | 647 | int old_index = q->indexOf(widget); 648 | if(old_index >= 0) // just move 649 | { 650 | if(index != old_index) 651 | { 652 | items.move(old_index, index); 653 | resetPages(); 654 | updateGeometries(); 655 | } 656 | } 657 | else 658 | { 659 | bool show = !(widget->isHidden() && widget->testAttribute(Qt::WA_WState_ExplicitShowHide)); 660 | 661 | ToolBoxItem *item = new ToolBoxItem(); 662 | item->tabContainer = new ToolBoxPageContainer(q); 663 | widget->setParent(item->tabContainer); 664 | widget->move(QPoint(0, 0)); 665 | item->widget = widget; 666 | item->tabTitle = createTitle(label, icon); 667 | item->handle = createHandle(); 668 | item->isExpanded = true; 669 | items.insert(index, item); 670 | if(show) 671 | widget->show(); 672 | 673 | item->calItemSize(); 674 | resetSizeHint(); 675 | connect(widget, &QWidget::destroyed, this, &AdvancedToolBoxPrivate::widgetDestroyed); 676 | doLayout(); 677 | } 678 | } 679 | 680 | void AdvancedToolBoxPrivate::doLayout() 681 | { 682 | Q_Q(AdvancedToolBox); 683 | if(!q->testAttribute(Qt::WA_Resized)) 684 | return; 685 | 686 | resetPages(); 687 | 688 | int space = 0; 689 | QVector expandedItems; 690 | { 691 | int totalSize = 0; 692 | for(auto item : items) 693 | { 694 | if(item->widget->isHidden()) 695 | continue; 696 | 697 | if(item->expanded()) 698 | expandedItems.append(item); 699 | 700 | item->layoutHeight = item->expanded() ? item->preferHeight() : 0; 701 | totalSize += item->tabTitle->sizeHint().height(); // title height 702 | totalSize += item->layoutHeight; 703 | totalSize += handleWidth; 704 | } 705 | // handle 要少一个 706 | totalSize -= (totalSize > 0 ? handleWidth : 0); 707 | space = q->rect().height() - totalSize; 708 | } 709 | 710 | if(space != 0) 711 | { 712 | int space2 = space; 713 | int viewsSize = 0; 714 | auto it = expandedItems.begin(); 715 | while(it != expandedItems.end()) 716 | { 717 | ToolBoxItem *item = *it; 718 | if((space > 0 && item->layoutHeight >= item->maxSize.height()) || 719 | (space < 0 && item->layoutHeight <= item->minSize.height())) 720 | { 721 | // remove fix size 722 | it = expandedItems.erase(it); 723 | } 724 | else 725 | { 726 | viewsSize += item->layoutHeight; 727 | it++; 728 | } 729 | } 730 | 731 | // 目前默认按照初始窗口当前尺寸来按比例分配空间 732 | // viewsSize变为0, 则认为layoutHeight都因为某些原因为0(不存在布局等) 733 | // 此时平均分配 734 | auto calcAddSize = [&viewsSize, &expandedItems](int curr, int space){ 735 | if(viewsSize <= 0) 736 | return qCeil(qreal(space) / expandedItems.count()); 737 | else 738 | return qCeil(qreal(space) * curr / viewsSize); 739 | }; 740 | 741 | // 空间不足或者有空余时,调整可伸缩的窗口,按照上一次手动调整后的比例,分配或者压缩空间 742 | // 先尝试按比例分配空间,超过最大、最小的先处理掉。 743 | bool done = false; 744 | while(!expandedItems.isEmpty() && !done) 745 | { 746 | done = true; 747 | auto it = expandedItems.begin(); 748 | while(it != expandedItems.end()) 749 | { 750 | ToolBoxItem *item = *it; 751 | int prefer = item->layoutHeight + calcAddSize(item->layoutHeight, space2); 752 | 753 | if((space > 0 && prefer > item->maxSize.height()) || (space < 0 && prefer < item->minSize.height())) 754 | { 755 | int threshold = space > 0 ? item->maxSize.height() : item->minSize.height(); 756 | space2 -= (threshold - item->layoutHeight); 757 | viewsSize -= item->layoutHeight; 758 | item->layoutHeight = threshold; 759 | expandedItems.erase(it); 760 | done = false; 761 | break; 762 | } 763 | it++; 764 | } 765 | } 766 | for(ToolBoxItem *item : expandedItems) 767 | { 768 | int add = calcAddSize(item->layoutHeight, space2); 769 | viewsSize -= item->layoutHeight; 770 | space2 -= add; 771 | item->layoutHeight += add; 772 | } 773 | } 774 | updateGeometries(); 775 | } 776 | 777 | void AdvancedToolBoxPrivate::expandStateChanged(int index, bool expand) 778 | { 779 | ToolBoxItem *curr = items.value(index); 780 | if(!curr) 781 | return; 782 | 783 | curr->tabTitle->setExpanded(expand); 784 | if(expand) 785 | { 786 | int target = curr->preferHeight(); 787 | int space = boxSpacing - target; 788 | if(space >= 0) 789 | { 790 | target += space; 791 | curr->layoutHeight = qMin(target, curr->maxSize.height()); 792 | } 793 | else 794 | { 795 | space = -space; 796 | const int count = items.count(); 797 | for(int i = count - 1; i >= 0 && space > 0; i--) 798 | { 799 | auto item = items.at(i); 800 | if(item->canResize() && i != index) // 暂时忽略当前page 801 | { 802 | int diff = qMin(item->layoutHeight - item->minSize.height(), space); 803 | item->layoutHeight -= diff; 804 | space -= diff; 805 | } 806 | } 807 | curr->layoutHeight = target; 808 | if(space > 0) 809 | { 810 | int diff = qMin(curr->layoutHeight - curr->minSize.height(), space); 811 | curr->layoutHeight -= diff; 812 | } 813 | } 814 | } 815 | else 816 | { 817 | int space = curr->manualHeight = curr->layoutHeight; 818 | curr->layoutHeight = 0; 819 | space += boxSpacing; 820 | if(space > 0) 821 | { 822 | const int count = items.count(); 823 | for(int i = count - 1; i >= 0 && space > 0; i--) 824 | { 825 | auto item = items.at(i); 826 | if(item->canResize()) 827 | { 828 | int diff = qMin(item->maxSize.height() - item->layoutHeight, space); 829 | item->layoutHeight += diff; 830 | space -= diff; 831 | } 832 | } 833 | } 834 | } 835 | 836 | curr->freezeTarget = true; 837 | updateGeometries(animationEnable); 838 | resetManualSize(); 839 | } 840 | 841 | void AdvancedToolBoxPrivate::moveHandle(int index, int distance) 842 | { 843 | if(distance == 0) 844 | return; 845 | 846 | // adjectHandle入口在鼠标移动事件,当鼠标按下时,resetManualSize将当前布局存储 847 | QVector shrink_part, expand_part; 848 | for(int i = index; i < items.count(); i++) 849 | { 850 | auto item = items.at(i); 851 | if(item->canResize()) 852 | expand_part.append(item); 853 | } 854 | for(int i = index - 1; i >= 0; i--) 855 | { 856 | auto item = items.at(i); 857 | if(item->canResize()) 858 | shrink_part.append(item); 859 | } 860 | 861 | if(distance > 0) // 交换一下 862 | std::swap(shrink_part, expand_part); 863 | 864 | int expand = 0, shrink = 0; 865 | for(ToolBoxItem *item : shrink_part) 866 | shrink += item->manualHeight - item->minSize.height(); 867 | 868 | for(ToolBoxItem *item : expand_part) 869 | expand += item->maxSize.height() - item->manualHeight; 870 | 871 | int min_dis = qMin(qMin(shrink, expand), abs(distance)); 872 | if(min_dis == 0) 873 | return; 874 | 875 | //调整 876 | int space = min_dis; 877 | for(int i = 0; i < shrink_part.count(); i++) 878 | { 879 | ToolBoxItem *item = shrink_part.at(i); 880 | int diff = qMin(item->manualHeight - item->minSize.height(), space); 881 | item->layoutHeight = item->manualHeight - diff; 882 | space -= diff; 883 | } 884 | 885 | space = min_dis; 886 | for(int i = 0; i < expand_part.count(); i++) 887 | { 888 | ToolBoxItem *item = expand_part.at(i); 889 | int diff = qMin(item->maxSize.height() - item->manualHeight, space); 890 | item->layoutHeight = item->manualHeight + diff; 891 | space -= diff; 892 | } 893 | updateGeometries(); 894 | } 895 | 896 | // 根据计算好的布局,设置窗口位置等 897 | void AdvancedToolBoxPrivate::updateGeometries(bool animate) 898 | { 899 | Q_Q(AdvancedToolBox); 900 | animate = animate && q->isVisible(); 901 | QParallelAnimationGroup *group = nullptr; 902 | if(isAnimationState) 903 | { 904 | nextIsAnimation = animate; 905 | return; 906 | } 907 | if(animate) 908 | { 909 | isAnimationState = true; 910 | group = new QParallelAnimationGroup(); 911 | QObject::connect(group, &QParallelAnimationGroup::finished, this, 912 | [this](){ 913 | isAnimationState = false; 914 | updateGeometries(nextIsAnimation); 915 | }); 916 | } 917 | 918 | const int hw = handleWidth; 919 | QRect cr = q->rect(); 920 | int x = cr.left(), offset = cr.top(), width = cr.width(); 921 | bool first = true; 922 | for(auto item : items) 923 | { 924 | if(item->isHidden()) 925 | continue; 926 | 927 | int th = item->tabTitle->sizeHint().height(); 928 | offset += (first ? 0 : hw); 929 | offset += th; 930 | 931 | int h = item->layoutHeight; 932 | QRect start = item->tabContainer->geometry(); 933 | QRect end(x, offset, width, h); 934 | 935 | bool freezeSize = item->freezeTarget; 936 | auto resizeTo = [q, item, th, hw, freezeSize](const QVariant &val) 937 | { 938 | QRect rect = val.toRect(); 939 | item->tabContainer->setGeometry(rect); 940 | if(!freezeSize) 941 | { 942 | item->widget->setGeometry(QRect(QPoint(0, 0), rect.size())); 943 | } 944 | rect = QRect(rect.left(), rect.top() - th, rect.width(), th); 945 | item->tabTitle->setGeometry(rect); 946 | 947 | if(item->handle->isVisibleTo(q)) 948 | { 949 | rect = QRect(rect.left(), rect.top() - hw, rect.width(), hw); 950 | if(hw <= 1) 951 | rect.adjust(0, -2, 0, 2); 952 | item->handle->setGeometry(rect); 953 | } 954 | }; 955 | if(freezeSize && item->expanded()) 956 | { 957 | item->widget->resize(end.size()); 958 | } 959 | 960 | if(animate && start != end) 961 | { 962 | QVariantAnimation *animation = new QVariantAnimation(group); 963 | animation->setDuration(100); 964 | animation->setStartValue(start); 965 | animation->setEndValue(end); 966 | connect(animation, &QVariantAnimation::valueChanged, this, resizeTo); 967 | group->addAnimation(animation); 968 | } 969 | else 970 | { 971 | resizeTo(end); 972 | } 973 | item->freezeTarget = false; 974 | offset += h; 975 | first = false; 976 | } 977 | boxSpacing = cr.bottom() - (offset - 1); 978 | if(group) 979 | { 980 | connect(group, &QVariantAnimation::finished, this, &AdvancedToolBoxPrivate::resetSizeHint); 981 | group->start(QAbstractAnimation::DeleteWhenStopped); 982 | } 983 | else 984 | { 985 | resetSizeHint(); 986 | } 987 | nextIsAnimation = false; 988 | } 989 | 990 | // 更新handle顺序以及重新设置隐藏和显示 991 | void AdvancedToolBoxPrivate::resetPages() 992 | { 993 | int count = items.count(); 994 | bool visible = false; 995 | for(int i = 0; i < count; i++) 996 | { 997 | auto item = items.at(i); 998 | item->tabTitle->setIndex(i); 999 | item->handle->setIndex(i); 1000 | if(item->isHidden()) 1001 | { 1002 | item->tabTitle->hide(); 1003 | item->tabContainer->hide(); 1004 | item->handle->setVisible(false); 1005 | } 1006 | else 1007 | { 1008 | item->tabTitle->show(); 1009 | item->tabContainer->show(); 1010 | item->handle->setVisible(visible); // 将第一个handle隐藏 1011 | visible = true; 1012 | } 1013 | } 1014 | } 1015 | 1016 | ToolBoxSplitterHandle *AdvancedToolBoxPrivate::createHandle() 1017 | { 1018 | Q_Q(AdvancedToolBox); 1019 | ToolBoxSplitterHandle *handle = new ToolBoxSplitterHandle(q); 1020 | handle->setAttribute(Qt::WA_MouseNoMask, true); 1021 | handle->setAutoFillBackground(false); 1022 | handle->setVisible(false); 1023 | handle->setCursor(Qt::SizeVerCursor); 1024 | // handle->installEventFilter(this); 1025 | return handle; 1026 | } 1027 | 1028 | ToolBoxTitle *AdvancedToolBoxPrivate::createTitle(const QString &label, const QIcon &icon) 1029 | { 1030 | Q_Q(AdvancedToolBox); 1031 | ToolBoxTitle *title = new ToolBoxTitle(label, icon, q); 1032 | 1033 | QObject::connect(title, &ToolBoxTitle::titleClicked, this, [this](int index) 1034 | { 1035 | auto item = items.value(index); 1036 | if(item) 1037 | setIndexExpand(index, !item->isExpanded); }); 1038 | 1039 | QObject::connect(title, &ToolBoxTitle::titleContextMenuRequest, this, &AdvancedToolBoxPrivate::showTitleMenu); 1040 | return title; 1041 | } 1042 | 1043 | void AdvancedToolBoxPrivate::showTitleMenu(int index, const QPoint &gpos) 1044 | { 1045 | Q_Q(AdvancedToolBox); 1046 | QMenu *menu = new QMenu(q); 1047 | menu->setAttribute(Qt::WA_DeleteOnClose); 1048 | QAction *hide_action = menu->addAction(tr("Hide")); 1049 | menu->addSeparator(); 1050 | int visible_count = 0; 1051 | QAction *first_checked = nullptr; 1052 | for(int i = 0; i < items.count(); i++) 1053 | { 1054 | auto item = items.at(i); 1055 | const bool hidden = item->isHidden(); 1056 | QAction *action = menu->addAction(item->title()); 1057 | action->setCheckable(true); 1058 | action->setChecked(!hidden); 1059 | if(!hidden) 1060 | { 1061 | visible_count++; 1062 | if(!first_checked) 1063 | first_checked = action; 1064 | if(index == i) 1065 | QObject::connect(hide_action, SIGNAL(triggered(bool)), action, SLOT(trigger())); 1066 | } 1067 | auto bind_slot = std::bind(&AdvancedToolBoxPrivate::setIndexVisible, this, i, std::placeholders::_1); 1068 | QObject::connect(action, &QAction::triggered, this, bind_slot); 1069 | } 1070 | 1071 | hide_action->setVisible(index >= 0); 1072 | hide_action->setEnabled(visible_count > 1); 1073 | if(visible_count == 1 && first_checked) 1074 | { 1075 | first_checked->setEnabled(false); 1076 | } 1077 | menu->exec(gpos); 1078 | } 1079 | 1080 | void AdvancedToolBoxPrivate::setDragRubberVisible(bool visible, const QRect &rect) 1081 | { 1082 | if(!dragRubber && visible) 1083 | { 1084 | Q_Q(AdvancedToolBox); 1085 | dragRubber = new QRubberBand(QRubberBand::Rectangle, q); 1086 | } 1087 | if(visible) 1088 | { 1089 | dragRubber->setGeometry(rect); 1090 | dragRubber->show(); 1091 | } 1092 | else if(dragRubber) 1093 | { 1094 | dragRubber->hide(); 1095 | } 1096 | } 1097 | 1098 | void AdvancedToolBoxPrivate::updateTitleIndent() 1099 | { 1100 | for(auto item : items) 1101 | { 1102 | item->tabTitle->update(); 1103 | } 1104 | } 1105 | 1106 | // 将当前当前布局尺寸保存 1107 | // 手动调整分割线位置、折叠展开,都会触发该逻辑 1108 | void AdvancedToolBoxPrivate::resetManualSize() 1109 | { 1110 | for(auto item : items) 1111 | { 1112 | if(item->canResize()) 1113 | { 1114 | item->manualHeight = item->layoutHeight; 1115 | } 1116 | } 1117 | } 1118 | 1119 | void AdvancedToolBoxPrivate::widgetDestroyed(QObject *o) 1120 | { 1121 | for(int i = 0; i < items.size(); i++) 1122 | { 1123 | if(items.at(i)->widget == o) 1124 | { 1125 | takeIndex(i); 1126 | break; 1127 | } 1128 | } 1129 | } 1130 | 1131 | void AdvancedToolBoxPrivate::resetSizeHint() 1132 | { 1133 | int handle_h = 0; 1134 | QSize min_size = QSize(0, 0); 1135 | QSize size = QSize(0, 0); 1136 | for(auto item : items) 1137 | { 1138 | if(item->widget->isHidden()) 1139 | continue; 1140 | if(item->expanded()) 1141 | { 1142 | min_size.rheight() += item->minSize.height(); 1143 | min_size.rwidth() = std::max(item->minSize.width(), min_size.width()); 1144 | 1145 | size.rheight() += item->sizeHint.height(); 1146 | size.rwidth() = std::max(item->sizeHint.width(), size.width()); 1147 | } 1148 | { 1149 | QSize title = item->tabTitle->sizeHint(); 1150 | min_size.rheight() += title.height(); 1151 | size.rheight() += title.height(); 1152 | } 1153 | handle_h += handleWidth; 1154 | } 1155 | min_size.rheight() += std::max(handle_h - handleWidth, 0); 1156 | size.rheight() += std::max(handle_h - handleWidth, 0); 1157 | if(minSizeHint != min_size || sizeHint != size) 1158 | { 1159 | minSizeHint = min_size; 1160 | sizeHint = size; 1161 | Q_Q(AdvancedToolBox); 1162 | q->updateGeometry(); 1163 | } 1164 | } 1165 | 1166 | ToolBoxSplitterHandle::ToolBoxSplitterHandle(AdvancedToolBox *parent) 1167 | : QWidget(parent) 1168 | { 1169 | } 1170 | 1171 | void ToolBoxSplitterHandle::setIndex(int index) 1172 | { 1173 | _index = index; 1174 | } 1175 | 1176 | int ToolBoxSplitterHandle::index() 1177 | { 1178 | return _index; 1179 | } 1180 | 1181 | void ToolBoxSplitterHandle::mouseMoveEvent(QMouseEvent *event) 1182 | { 1183 | if(pressed) 1184 | { 1185 | QPoint pos = event->globalPos(); 1186 | AdvancedToolBox *box = static_cast(parentWidget()); 1187 | box->d_ptr->moveHandle(_index, pos.y() - moveStart.y()); 1188 | } 1189 | } 1190 | 1191 | void ToolBoxSplitterHandle::mousePressEvent(QMouseEvent *event) 1192 | { 1193 | if(event->buttons() == Qt::LeftButton) 1194 | { 1195 | pressed = true; 1196 | moveStart = event->globalPos(); 1197 | AdvancedToolBox *box = static_cast(parentWidget()); 1198 | box->d_ptr->resetManualSize(); 1199 | } 1200 | } 1201 | 1202 | void ToolBoxSplitterHandle::mouseReleaseEvent(QMouseEvent *event) 1203 | { 1204 | if(event->button() == Qt::LeftButton) 1205 | { 1206 | pressed = false; 1207 | AdvancedToolBox *box = static_cast(parentWidget()); 1208 | box->d_ptr->resetManualSize(); 1209 | } 1210 | } 1211 | 1212 | 1213 | 1214 | ToolBoxTitle::ToolBoxTitle(const QString &label, const QIcon &icon, AdvancedToolBox *parent) 1215 | : QAbstractButton(parent) 1216 | { 1217 | setText(label); 1218 | setIcon(icon); 1219 | setIconSize(QSize(16, 16)); 1220 | connect(this, &ToolBoxTitle::clicked, this, [this]() 1221 | { emit titleClicked(tabIndex); }); 1222 | setContextMenuPolicy(Qt::CustomContextMenu); 1223 | connect(this, &ToolBoxTitle::customContextMenuRequested, this, [this](const QPoint &pos) 1224 | { emit titleContextMenuRequest(tabIndex, this->mapToGlobal(pos)); }); 1225 | 1226 | setAttribute(Qt::WA_Hover); 1227 | } 1228 | 1229 | QSize ToolBoxTitle::sizeHint() const 1230 | { 1231 | if(_sizeHint.isValid()) 1232 | return _sizeHint; 1233 | 1234 | ensurePolished(); 1235 | QStyleOptionTab opt; 1236 | opt.initFrom(this); 1237 | if(this->expanded) 1238 | opt.state |= QStyle::State_On; 1239 | if(this->underMouse()) 1240 | opt.state |= QStyle::State_MouseOver; 1241 | 1242 | QWidget *parent = parentWidget(); 1243 | int w = style()->pixelMetric(QStyle::PM_TabBarTabHSpace, &opt, parent); 1244 | int h = style()->pixelMetric(QStyle::PM_TabBarTabVSpace, &opt, parent); 1245 | 1246 | bool nullicon = this->icon().isNull(); 1247 | QSize icon_size = nullicon ? QSize(0, 0) : this->iconSize(); 1248 | w += icon_size.width(); 1249 | w += nullicon ? 0 : 4; 1250 | 1251 | const QFontMetrics fm = fontMetrics(); 1252 | w += fm.size(0, this->text()).width(); 1253 | h += qMax(fm.height(), icon_size.height()); 1254 | _sizeHint = style()->sizeFromContents(QStyle::CT_TabBarTab, &opt, QSize(w, h), parent); 1255 | return _sizeHint; 1256 | } 1257 | 1258 | bool ToolBoxTitle::event(QEvent *e) 1259 | { 1260 | switch(e->type()) 1261 | { 1262 | case QEvent::HoverMove: 1263 | case QEvent::HoverEnter: 1264 | { 1265 | int indent = static_cast(parentWidget())->textIndentation(); 1266 | QHoverEvent *he = static_cast(e); 1267 | QRect rect = this->rect(); 1268 | rect.setRight(rect.left() + indent); 1269 | bool test = rect.contains(he->pos()); 1270 | if(hoverBranch != test) 1271 | { 1272 | hoverBranch = test; 1273 | update(); 1274 | } 1275 | } 1276 | break; 1277 | case QEvent::HoverLeave: 1278 | hoverBranch = false; 1279 | break; 1280 | case QEvent::MouseButtonPress: 1281 | { 1282 | QMouseEvent *me = static_cast(e); 1283 | if(me->buttons() == Qt::LeftButton) 1284 | { 1285 | pressed = true; 1286 | pressedPos = me->globalPos(); 1287 | } 1288 | } 1289 | break; 1290 | case QEvent::MouseMove: 1291 | if(pressed) 1292 | { 1293 | QMouseEvent *me = static_cast(e); 1294 | if((me->globalPos() - pressedPos).manhattanLength() > QApplication::startDragDistance()) 1295 | { 1296 | setDown(false); 1297 | qobject_cast(parentWidget())->startDrag(tabIndex, pressedPos); 1298 | return true; 1299 | } 1300 | } 1301 | break; 1302 | case QEvent::MouseButtonRelease: 1303 | if(static_cast(e)->button() == Qt::LeftButton) 1304 | pressed = false; 1305 | break; 1306 | default: 1307 | break; 1308 | } 1309 | return QAbstractButton::event(e); 1310 | } 1311 | 1312 | void ToolBoxTitle::initStyleOption(QStyleOptionToolBox *option) const 1313 | { 1314 | if(!option) 1315 | return; 1316 | option->initFrom(this); 1317 | 1318 | option->text = text(); 1319 | option->icon = icon(); 1320 | if(isDown()) 1321 | option->state |= QStyle::State_Sunken; 1322 | if(this->expanded) 1323 | option->state |= QStyle::State_Open; 1324 | } 1325 | 1326 | void ToolBoxTitle::paintEvent(QPaintEvent *) 1327 | { 1328 | QWidget *parent = parentWidget(); 1329 | 1330 | QStyleOptionToolBox tabopt; 1331 | initStyleOption(&tabopt); 1332 | 1333 | QPainter painter(this); 1334 | style()->drawControl(QStyle::CE_ToolBoxTabShape, &tabopt, &painter, parent); 1335 | 1336 | int indent = static_cast(parentWidget())->textIndentation(); 1337 | // draw branch 1338 | if(indent > 0) 1339 | { 1340 | QStyleOptionViewItem branchopt; 1341 | branchopt.rect = tabopt.rect; 1342 | branchopt.state = tabopt.state; 1343 | branchopt.state &= ~QStyle::State_MouseOver; 1344 | branchopt.state |= hoverBranch ? QStyle::State_MouseOver : QStyle::State_None; 1345 | branchopt.state |= QStyle::State_Children; 1346 | branchopt.rect.setRight(tabopt.rect.left() + indent); 1347 | style()->drawPrimitive(QStyle::PE_IndicatorBranch, &branchopt, &painter, parent); 1348 | } 1349 | tabopt.rect.setLeft(tabopt.rect.left() + indent); 1350 | 1351 | // draw icon 1352 | bool null_icon = this->icon().isNull(); 1353 | int icon_width = this->iconSize().width(); 1354 | if(!null_icon) 1355 | { 1356 | QRect cr = style()->subElementRect(QStyle::SE_ToolBoxTabContents, &tabopt, parent); 1357 | cr.setWidth(icon_width + 2); 1358 | cr.moveLeft(cr.left() + 2); 1359 | QIcon::Mode mode = tabopt.state & QStyle::State_Enabled ? QIcon::Normal : QIcon::Disabled; 1360 | if(mode == QIcon::Normal && tabopt.state & QStyle::State_HasFocus) 1361 | mode = QIcon::Active; 1362 | QIcon::State state = tabopt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; 1363 | this->icon().paint(&painter, cr, Qt::AlignCenter, mode, state); 1364 | tabopt.rect.adjust(icon_width + 4, 0, 0, 0); 1365 | } 1366 | 1367 | // draw text 1368 | if(!tabopt.text.isEmpty()) 1369 | { 1370 | tabopt.icon = QIcon(); 1371 | // QStyleOptionToolBox固定左对齐。可以考虑使用 QStyleOptionTab 绘制文本,以支持样式对齐,不过默认是居中样式。 1372 | style()->drawControl(QStyle::CE_ToolBoxTabLabel, &tabopt, &painter, parent); 1373 | } 1374 | } 1375 | 1376 | #include "advancedtoolbox.moc" 1377 | --------------------------------------------------------------------------------