Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gui: support dragging of toolbar inside status/menu bar #13973

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 0 additions & 8 deletions src/Gui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
#include "Tools.h"
#include <App/Color.h>

FC_LOG_LEVEL_INIT("MainWindow",false,true,true)

Check warning on line 114 in src/Gui/MainWindow.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

variable '_s_fclvl' is non-const and globally accessible, consider making it const [cppcoreguidelines-avoid-non-const-global-variables]

#if defined(Q_OS_WIN32)
#define slots
Expand All @@ -122,7 +122,7 @@
using namespace std;


MainWindow* MainWindow::instance = nullptr;

Check warning on line 125 in src/Gui/MainWindow.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

variable 'instance' is non-const and globally accessible, consider making it const [cppcoreguidelines-avoid-non-const-global-variables]

Check warning on line 125 in src/Gui/MainWindow.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

variable 'instance' provides global access to a non-const object; consider making the pointed-to data 'const' [cppcoreguidelines-avoid-non-const-global-variables]

namespace Gui {

Expand All @@ -134,10 +134,10 @@
* This makes the usage of StatusBarObserver thread-safe.
* @author Werner Mayer
*/
class CustomMessageEvent : public QEvent

Check warning on line 137 in src/Gui/MainWindow.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

class 'CustomMessageEvent' defines a default destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move assignment operator [cppcoreguidelines-special-member-functions]
{
public:
CustomMessageEvent(int t, const QString& s, int timeout=0)

Check warning on line 140 in src/Gui/MainWindow.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

parameter name 't' is too short, expected at least 2 characters [readability-identifier-length]

Check warning on line 140 in src/Gui/MainWindow.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

pass by value and use std::move [modernize-pass-by-value]

Check warning on line 140 in src/Gui/MainWindow.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

parameter name 's' is too short, expected at least 2 characters [readability-identifier-length]
: QEvent(QEvent::User), _type(t), msg(s), _timeout(timeout)
{ }
~CustomMessageEvent() override = default;
Expand All @@ -158,7 +158,7 @@
* - Allow application to display dimension information such as the viewportsize
* - Provide a popup menu allowing user to change the used unit schema (and update if changed elsewhere)
*/
class DimensionWidget : public QPushButton, WindowParameter

Check warning on line 161 in src/Gui/MainWindow.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

class 'DimensionWidget' defines a non-default destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move assignment operator [cppcoreguidelines-special-member-functions]
{
Q_OBJECT

Expand Down Expand Up @@ -333,7 +333,7 @@
class MainWindowTabBar : public QTabBar
{
public:
MainWindowTabBar(QWidget *parent) : QTabBar(parent)

Check warning on line 336 in src/Gui/MainWindow.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

Single-parameter constructors should be marked explicit. [runtime/explicit] [5]
{
setExpanding(false);
}
Expand Down Expand Up @@ -801,14 +801,6 @@
populateDockWindowMenu(menu);
menu->addSeparator();
populateToolBarMenu(menu);
QMenu *undockMenu = new QMenu(menu);
ToolBarManager::getInstance()->populateUndockMenu(undockMenu);
if (undockMenu->actions().isEmpty()) {
delete undockMenu;
}
else {
menu->addMenu(undockMenu);
}
menu->addSeparator();
Workbench* wb = WorkbenchManager::instance()->active();
if (wb) {
Expand Down Expand Up @@ -2676,5 +2668,5 @@
}


#include "moc_MainWindow.cpp"

Check failure on line 2671 in src/Gui/MainWindow.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

'moc_MainWindow.cpp' file not found [clang-diagnostic-error]
#include "MainWindow.moc"
230 changes: 187 additions & 43 deletions src/Gui/ToolBarManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
# include <QHBoxLayout>
# include <QMenuBar>
# include <QMouseEvent>
# include <QPainter>
# include <QPointer>
# include <QStatusBar>
# include <QToolBar>
# include <QToolButton>
# include <QStyleOption>
#endif

#include <boost/algorithm/string/predicate.hpp>
Expand Down Expand Up @@ -188,6 +190,7 @@
{
if (_layout->indexOf(w) < 0) {
_layout->addWidget(w);
ToolBarManager::getInstance()->setToolBarMovable(qobject_cast<QToolBar*>(w));
adjustParent();
QString name = w->objectName();
if (!name.isEmpty()) {
Expand All @@ -207,6 +210,7 @@
_layout->removeWidget(w);
}
_layout->insertWidget(idx, w);
ToolBarManager::getInstance()->setToolBarMovable(qobject_cast<QToolBar*>(w));
adjustParent();
saveState();
}
Expand All @@ -221,6 +225,12 @@
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();
}
}
QString name = w->objectName();
if (!name.isEmpty()) {
Base::ConnectionBlocker block(_conn);
Expand Down Expand Up @@ -294,10 +304,137 @@
boost::signals2::scoped_connection &_conn;
};

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

Comment on lines +307 to +315
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I do understand benefit of having our own ToolBar widget (it would come handy for a lot of reasons) - I don't understand why you create this class now while its only method is a simple call to parent implementation. I'd suggest removing this method and adding other useful helpers like float(). Override the moveable behavior etc.

} // 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);
Comment on lines +327 to +328
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose that we simply don't support vertical toolbars - It would be nice to state it in the comment though


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

QAction *ToolBarGrip::action() const
{
return _action;
}

void ToolBarGrip::paintEvent(QPaintEvent*)
{
QPainter painter(this);
if (auto toolbar = qobject_cast<QToolBar*>(parentWidget())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not ToolBar*? You do static_cast after it anyway so you assume that it in fact is the ToolBar*.

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this commented code to not confuse newcomers.

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);
Comment on lines +355 to +359
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is drawing of this widget different when it is not in the custom toolbar area?

}
}

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);
getMainWindow()->addToolBar(toolbar);
toolbar->setWindowFlags(Qt::Tool
| Qt::FramelessWindowHint
| Qt::X11BypassWindowManagerHint);
toolbar->adjustSize();
pos = toolbar->mapFromGlobal(pos);
toolbar->move(pos.x()-10, pos.y()-10);
toolbar->setVisible(true);
}
toolbar->topLevelChanged(true);
Comment on lines +380 to +392
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you already have custom ToolBar class it should be some method of that class, like void float().

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you don't have to mark signals with explicit Q_EMIT it is nice at it clearly states the intention of that call. Also a comment why it is outside of the block above (to not block this signal - a bug that you have in the previous PR) would be nice as it once again is hard to quickly understand why every call is in that block and it is outside.


// 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

Check warning on line 415 in src/Gui/ToolBarManager.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

Mose ==> More, Mouse, Mode
// 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::_instance = nullptr; // NOLINT

ToolBarManager* ToolBarManager::getInstance()
Expand Down Expand Up @@ -756,6 +893,18 @@
menuBarLeftArea->restoreState(mbLeftToolBars);
}

ToolBarArea *ToolBarManager::getToolBarArea(QToolBar *toolbar) const
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #13721 I already have such method, but it does more like Qt does it so via enum. This method also covers standard Qt::ToolBarArea's: https://github.com/FreeCAD/FreeCAD/pull/13721/files#diff-dafe6a032a9dc3c2149c60fc7319fbdcda9b32e255a21185f62849cc8ba41902R438. Can you rework your code to be more inline with it so we won't have conflicts?

{
auto parent = toolbar->parentWidget();
if (parent == statusBarArea)
return statusBarArea;
if (parent == menuBarLeftArea)
return menuBarLeftArea;
if (parent == menuBarRightArea)
return menuBarRightArea;
return nullptr;
}

bool ToolBarManager::addToolBarToArea(QObject *source, QMouseEvent *ev)
{
auto statusBar = getMainWindow()->statusBar();
Expand Down Expand Up @@ -887,47 +1036,9 @@
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();
auto undockAction = new QAction(menu);
undockAction->setText(action->text());
undockAction->setToolTip(tooltip);
menu->addAction(undockAction);
QObject::connect(undockAction, &QAction::triggered, [area, toolbar]() {
if (toolbar->parentWidget() == getMainWindow()) {
return;
}
auto pos = toolbar->mapToGlobal(QPoint(0, 0));
QSignalBlocker blocker(toolbar);
area->removeWidget(toolbar);
getMainWindow()->addToolBar(toolbar);
toolbar->setWindowFlags(Qt::Tool
| Qt::FramelessWindowHint
| Qt::X11BypassWindowManagerHint);
toolbar->move(pos.x(), pos.y()-toolbar->height()-10);
toolbar->adjustSize();
toolbar->setVisible(true);
Q_EMIT toolbar->topLevelChanged(true);
});
};
if (area) {
area->foreachToolBar(addMenuUndockItem);
}
else {
statusBarArea->foreachToolBar(addMenuUndockItem);
menuBarLeftArea->foreachToolBar(addMenuUndockItem);
menuBarRightArea->foreachToolBar(addMenuUndockItem);
}
}

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

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

if (!menuUndock.actions().empty()) {
menu.addSeparator();
menu.addMenu(&menuUndock);
}
menu.exec(QCursor::pos());
return true;
}
Expand Down Expand Up @@ -1097,9 +1203,47 @@
{
for (auto& tb : toolBars()) {
tb->setMovable(moveable);
setToolBarMovable(tb);
}
}

void ToolBarManager::setToolBarMovable(QToolBar *toolbar) const
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you already added custom ToolBar class, would it be vise to move that logic there? Having it separated can cause a lot of confusion when you don't know that you have to call this function.

I wonder if it would not be better to make this function as handler for movableChanged signal of QToolBar: https://doc.qt.io/qt-6/qtoolbar.html#movableChanged.

At least it should have more descriptive name because for the whole review process I was sure that it calls setMovable() in the toolbar while it does not.

{
if (!toolbar)
return;
Comment on lines +1212 to +1213
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that this classes do not have automatic code formatting enabled - but it would be nice to adhere to the formatting requested by the project.


bool movable = toolbar->isMovable();

if (auto area = getToolBarArea(toolbar)) {
if (auto grip = toolbar->findChild<ToolBarGrip*>()) {
if (!movable) {
toolbar->removeAction(grip->action());
grip->deleteLater();
}
}
else {
new ToolBarGrip(toolbar);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating new object without assigning it to anything is a very strange construction. Probably it is fine as it has properly defined parent so Qt will take care of it but nonetheless it is quite odd - please at least put comment over it that it is intentional and how it is supposed to work.

}
area->adjustParent();
}
QString name = QStringLiteral("_fc_toolbar_sep_");
auto sep = toolbar->findChild<QAction*>(name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto separator - let's not use such short names, it makes code harder to read and understand.

if (sep) {
if (movable) {
Comment on lines +1231 to +1232
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not if (sep && movable) ?

toolbar->removeAction(sep);
sep->deleteLater();
}
}
else if (!movable) {
auto actions = toolbar->actions();
if (actions.size()) {
sep = toolbar->insertSeparator(actions[0]);
sep->setObjectName(name);
}
}
}


QToolBar* ToolBarManager::findToolBar(const QList<QToolBar*>& toolbars, const QString& item) const
{
for (QToolBar* it : toolbars) {
Expand Down Expand Up @@ -1229,4 +1373,4 @@
}
}

#include "moc_ToolBarManager.cpp"

Check failure on line 1376 in src/Gui/ToolBarManager.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

'moc_ToolBarManager.cpp' file not found [clang-diagnostic-error]
24 changes: 23 additions & 1 deletion src/Gui/ToolBarManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
#include <boost_signals2.hpp>

#include <QStringList>
#include <QPointer>
#include <QTimer>
#include <QWidget>

#include <FCGlobal.h>
#include <Base/Parameter.h>
Expand Down Expand Up @@ -86,6 +88,25 @@
QList<ToolBarItem*> _items;
};

class ToolBarGrip: public QWidget
{
Q_OBJECT
public:
ToolBarGrip(QToolBar *);

Check warning on line 95 in src/Gui/ToolBarManager.h

View workflow job for this annotation

GitHub Actions / Lint / Lint

Single-parameter constructors should be marked explicit. [runtime/explicit] [5]

QAction *action() const;

protected:
void paintEvent(QPaintEvent*);
void mouseMoveEvent(QMouseEvent *);
void mousePressEvent(QMouseEvent *);
void mouseReleaseEvent(QMouseEvent *);

private:
QPointer<QAction> _action;
};


/**
* The ToolBarManager class is responsible for the creation of toolbars and appending them
* to the main window.
Expand Down Expand Up @@ -123,7 +144,8 @@
int toolBarIconSize(QWidget *widget=nullptr) const;
void setupToolBarIconSize();

void populateUndockMenu(QMenu *menu, ToolBarArea *area = nullptr);
ToolBarArea *getToolBarArea(QToolBar *) const;
void setToolBarMovable(QToolBar *) const;

protected:
void setup(ToolBarItem*, QToolBar*) const;
Expand Down