Skip to content

Commit

Permalink
Gui: support dragging of toolbar inside status/menu bar
Browse files Browse the repository at this point in the history
  • Loading branch information
realthunder committed May 11, 2024
1 parent d2b55a3 commit 0e9cf4a
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 72 deletions.
5 changes: 0 additions & 5 deletions src/Gui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,6 @@ void populateMenu(QMenu *menu, MenuType type, bool popup,
QString tooltip, ltooltip;
QMenu *hiddenMenu = nullptr;
QMenu *lockMenu = nullptr;
QMenu *undockMenu = nullptr;
menu->setToolTipsVisible(true);

if (ViewParams::getEnableMenuBarCheckBox())
Expand All @@ -790,8 +789,6 @@ void populateMenu(QMenu *menu, MenuType type, bool popup,
!ToolBarManager::getInstance()->isDefaultMovable(), &checkbox);
QObject::connect(lockAction, &QAction::toggled, cb);
QObject::connect(checkbox, &QCheckBox::toggled, cb);
undockMenu = new QMenu(menu);
ToolBarManager::getInstance()->populateUndockMenu(undockMenu);
} else {
lockAction = lockMenu->addAction(QObject::tr("Default"));
QObject::connect(lockAction, &QAction::toggled, cb);
Expand Down Expand Up @@ -945,8 +942,6 @@ void populateMenu(QMenu *menu, MenuType type, bool popup,
}
}

if (undockMenu && !undockMenu->actions().isEmpty())
menu->addMenu(undockMenu);
if (hiddenMenu)
menu->addMenu(hiddenMenu);
if (lockMenu)
Expand Down
248 changes: 182 additions & 66 deletions src/Gui/ToolBarManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
# include <QMouseEvent>
# include <QCheckBox>
# include <QPointer>
# include <QPainter>
# include <QStatusBar>
# include <QMenuBar>
#endif
Expand Down Expand Up @@ -223,6 +224,7 @@ class ToolBarArea : public QWidget
{
if (_layout->indexOf(w) < 0) {
_layout->addWidget(w);
ToolBarManager::getInstance()->setToolBarMovable(qobject_cast<QToolBar*>(w));
adjustParentSize();
QString name = w->objectName();
if (!name.isEmpty()) {
Expand All @@ -240,6 +242,7 @@ class ToolBarArea : public QWidget
if (index > 0)
_layout->removeWidget(w);
_layout->insertWidget(idx, w);
ToolBarManager::getInstance()->setToolBarMovable(qobject_cast<QToolBar*>(w));
adjustParentSize();
saveState();
}
Expand All @@ -254,6 +257,12 @@ class ToolBarArea : public QWidget
void removeWidget(QWidget *w)
{
_layout->removeWidget(w);
if (auto tb = qobject_cast<QToolBar*>(w)) {
if (auto grip = tb->findChild<ToolBarGrip*>()) {
tb->removeAction(grip->action());
grip->deleteLater();
}
}
adjustParentSize();
QString name = w->objectName();
if (!name.isEmpty()) {
Expand Down Expand Up @@ -325,8 +334,133 @@ class ToolBarArea : public QWidget
boost::signals2::scoped_connection &_conn;
};

class ToolBar: public QToolBar
{
public:
void initStyleOption(QStyleOptionToolBar *option) const
{
QToolBar::initStyleOption(option);
}
};

} // namespace Gui


//////////////////////////////////////////////////////////////////////

ToolBarGrip::ToolBarGrip(QToolBar * parent)
:QWidget(parent)
{
QStyle *style = parent->style();
QStyleOptionToolBar opt;
static_cast<ToolBar*>(parent)->initStyleOption(&opt);
opt.features = QStyleOptionToolBar::Movable;
int width = style->subElementRect(QStyle::SE_ToolBarHandle, &opt, parent).width();
this->setFixedWidth(width+4);

setCursor(Qt::OpenHandCursor);
setMouseTracking(true);
auto actions = parent->actions();
_action = parent->insertWidget(actions.isEmpty()?nullptr:actions[0], this);
setVisible(true);
}

void ToolBarGrip::paintEvent(QPaintEvent*)
{
QPainter painter(this);
if (auto toolbar = qobject_cast<QToolBar*>(parentWidget())) {
QStyle *style = toolbar->style();
QStyleOptionToolBar opt;
static_cast<ToolBar*>(toolbar)->initStyleOption(&opt);
opt.features = QStyleOptionToolBar::Movable;
// opt.rect = style->subElementRect(QStyle::SE_ToolBarHandle, &opt, toolbar);
opt.rect = this->rect();
style->drawPrimitive(QStyle::PE_IndicatorToolBarHandle, &opt, &painter, toolbar);
}
else {
painter.setPen(Qt::transparent);
painter.setOpacity(0.5);
painter.setBrush(QBrush(Qt::black, Qt::Dense6Pattern));
QRect rect(this->rect());
painter.drawRect(rect);
}
}

void ToolBarGrip::mouseMoveEvent(QMouseEvent *me)
{
auto toolbar = qobject_cast<QToolBar*>(parentWidget());
if (!toolbar) {
return;
}
auto area = ToolBarManager::getInstance()->getToolBarArea(toolbar);
if (!area)
return;

QPoint pos = me->globalPos();
QRect rect(toolbar->mapToGlobal(QPoint(0,0)), toolbar->size());
if (rect.contains(pos))
return;

// If mouse is out of the toolbar, remove the toolbar from area and float the
// toolbar
{
QSignalBlocker blocker(toolbar);
area->removeWidget(toolbar);
ToolBarManager::getInstance()->addToolBarToMainWindow(toolbar);
toolbar->setWindowFlags(Qt::Tool
| Qt::FramelessWindowHint
| Qt::X11BypassWindowManagerHint);
toolbar->adjustSize();
pos = toolbar->mapFromGlobal(pos);
toolbar->move(pos.x()-10, pos.y()-10);
ToolBarManager::getInstance()->setToolBarVisible(toolbar, true);
}
toolbar->topLevelChanged(true);

// After removing from area, this grip will be deleted. In order to
// continue toolbar dragging (because the mouse button is still pressed),
// we fake mouse events and send to toolbar. For some reason,
// send/postEvent() does not work, only timer works.
QPointer tb(toolbar);
QTimer::singleShot(0, [tb] {
auto modifiers = QApplication::queryKeyboardModifiers();
auto buttons = QApplication::mouseButtons();
if (buttons != Qt::LeftButton
|| QWidget::mouseGrabber()
|| modifiers != Qt::NoModifier
|| !tb) {
return;
}
QPoint pos(10, 10);
QPoint globalPos(tb->mapToGlobal(pos));
QMouseEvent mouseEvent(
QEvent::MouseButtonPress,
pos, globalPos, Qt::LeftButton, buttons, modifiers);
QApplication::sendEvent(tb, &mouseEvent);

// Mose follow the mouse press event with mouse move with some offset
// in order to activate toolbar dragging.
QPoint offset(30, 30);
QMouseEvent mouseMoveEvent(
QEvent::MouseMove,
pos+offset, globalPos+offset,
Qt::LeftButton, buttons, modifiers);
QApplication::sendEvent(tb, &mouseMoveEvent);
});
}

void ToolBarGrip::mousePressEvent(QMouseEvent *)
{
setCursor(Qt::ClosedHandCursor);
}

void ToolBarGrip::mouseReleaseEvent(QMouseEvent *)
{
setCursor(Qt::OpenHandCursor);
}

//////////////////////////////////////////////////////////////////////

ToolBarManager::ToolBarManager()
{
hGeneral = App::GetApplication().GetUserParameter().GetGroup(
Expand Down Expand Up @@ -628,6 +762,7 @@ void ToolBarManager::onTimer()
auto curArea = isGlobal ? gArea : area;
QByteArray name = v.first.toUtf8();
int idx = hStatusBar->GetInt(name, -1);
tb->setMovable(hMovable->GetBool(name, movable));

if (idx >= 0) {
sbToolBars[idx] = tb;
Expand All @@ -647,15 +782,13 @@ void ToolBarManager::onTimer()
}

if (tb->parentWidget() != getMainWindow()) {
Base::StateLocker guard(adding);
getMainWindow()->addToolBar(tb);
addToolBarToMainWindow(tb);
}

if (defArea != curArea && mw->toolBarArea(tb) == defArea)
lines.emplace(ToolBarKey(tb),tb);
if (tb->toggleViewAction()->isVisible())
setToolBarVisible(tb, hPref->GetBool(name, tb->isVisible()));
tb->setMovable(hMovable->GetBool(name, movable));
}

bool first = true;
Expand Down Expand Up @@ -833,8 +966,7 @@ void ToolBarManager::setup(ToolBarItem* toolBarItems)
if (toolbar_added) {
auto area = getMainWindow()->toolBarArea(toolbar);
if (!isToolBarAllowed(toolbar, area)) {
Base::StateLocker guard(adding);
getMainWindow()->addToolBar(toolbar);
addToolBarToMainWindow(toolbar);
}

int &top_width = widths[area];
Expand Down Expand Up @@ -967,8 +1099,7 @@ void ToolBarManager::restoreState()
continue;
}
if (toolbar->parentWidget() != getMainWindow()) {
Base::StateLocker guard(adding);
getMainWindow()->addToolBar(toolbar);
addToolBarToMainWindow(toolbar);
}
}

Expand All @@ -978,6 +1109,24 @@ void ToolBarManager::restoreState()
restored = true;
}

void ToolBarManager::addToolBarToMainWindow(QToolBar *toolbar)
{
Base::StateLocker guard(adding);
getMainWindow()->addToolBar(toolbar);
}

ToolBarArea *ToolBarManager::getToolBarArea(QToolBar *toolbar)
{
auto parent = toolbar->parentWidget();
if (parent == statusBarArea)
return statusBarArea;
if (parent == menuBarLeftArea)
return menuBarLeftArea;
if (parent == menuBarRightArea)
return menuBarRightArea;
return nullptr;
}

void ToolBarManager::retranslate()
{
for (auto &v : toolBars()) {
Expand Down Expand Up @@ -1092,22 +1241,43 @@ void ToolBarManager::onToggleStatusBarWidget(QWidget *w, bool visible)
hStatusBar->SetBool(w->objectName().toUtf8().constData(), w->isVisible());
}

void ToolBarManager::onMovableChanged(bool movable)
void ToolBarManager::onMovableChanged(bool)
{
setToolBarMovable(qobject_cast<QToolBar*>(sender()));
}

void ToolBarManager::setToolBarMovable(QToolBar *toolbar)
{
auto toolbar = qobject_cast<QToolBar*>(sender());
if (!toolbar)
return;

bool movable = toolbar->isMovable() && isDefaultMovable();

if (restored && !toolbar->objectName().isEmpty()) {
Base::ConnectionBlocker block(connParam);
hMovable->SetBool(toolbar->objectName().toUtf8(), movable);
}
if (auto area = getToolBarArea(toolbar)) {
if (auto grip = toolbar->findChild<ToolBarGrip*>()) {
if (!movable) {
toolbar->removeAction(grip->action());
grip->deleteLater();
}
}
else {
new ToolBarGrip(toolbar);
}
area->adjustParentSize();
}
QString name = QStringLiteral("_fc_toolbar_sep_");
auto sep = toolbar->findChild<QAction*>(name);
if (sep) {
toolbar->removeAction(sep);
sep->deleteLater();
if (movable) {
toolbar->removeAction(sep);
sep->deleteLater();
}
}
if (!movable) {
else if (!movable) {
auto actions = toolbar->actions();
if (actions.size()) {
sep = toolbar->insertSeparator(actions[0]);
Expand Down Expand Up @@ -1248,57 +1418,9 @@ bool ToolBarManager::addToolBarToArea(QObject *o, QMouseEvent *ev)
return false;
}

void ToolBarManager::populateUndockMenu(QMenu *menu, ToolBarArea *area)
{
menu->setTitle(tr("Undock toolbars"));
auto tooltip = QObject::tr("Undock from status bar");
auto addMenuUndockItem = [&](QToolBar *toolbar, int, ToolBarArea *area) {
auto *action = toolbar->toggleViewAction();
QCheckBox *checkbox;
auto wa = Action::addCheckBox(menu, action->text(),
tooltip, action->icon(), true, &checkbox);
QObject::connect(wa, &QAction::toggled, [checkbox](bool checked){
QSignalBlocker block(checkbox);
checkbox->setChecked(checked);
});
QObject::connect(wa, &QWidgetAction::toggled, [area, toolbar, this](bool checked) {
if (checked) {
QSignalBlocker blocker(toolbar);
area->addWidget(toolbar);
setToolBarVisible(toolbar, true);
} else if (toolbar->parentWidget() == getMainWindow())
return;
else {
auto pos = toolbar->mapToGlobal(QPoint(0, 0));
QSignalBlocker blocker(toolbar);
area->removeWidget(toolbar);
{
Base::StateLocker guard(adding);
getMainWindow()->addToolBar(toolbar);
}
toolbar->setWindowFlags(Qt::Tool
| Qt::FramelessWindowHint
| Qt::X11BypassWindowManagerHint);
toolbar->move(pos.x(), pos.y()-toolbar->height()-10);
toolbar->adjustSize();
setToolBarVisible(toolbar, true);
}
toolbar->topLevelChanged(!checked);
});
};
if (area)
area->foreachToolBar(addMenuUndockItem);
else {
statusBarArea->foreachToolBar(addMenuUndockItem);
menuBarLeftArea->foreachToolBar(addMenuUndockItem);
menuBarRightArea->foreachToolBar(addMenuUndockItem);
}
}

bool ToolBarManager::showContextMenu(QObject *source)
{
QMenu menu;
QMenu menuUndock;
QHBoxLayout *layout = nullptr;
ToolBarArea *area;
if (getMainWindow()->statusBar() == source) {
Expand Down Expand Up @@ -1367,12 +1489,6 @@ bool ToolBarManager::showContextMenu(QObject *source)
}

area->foreachToolBar(addMenuVisibleItem);
populateUndockMenu(&menuUndock, area);

if (menuUndock.actions().size()) {
menu.addSeparator();
menu.addMenu(&menuUndock);
}
menu.exec(QCursor::pos());
return true;
}
Expand Down
Loading

0 comments on commit 0e9cf4a

Please sign in to comment.