From b006a370104fd193a4e2f85c2619f801d30ff079 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 22 Jan 2025 15:28:46 +0100 Subject: [PATCH 01/23] Gui: Add Action::setBlockedChecked Remove the second parameter of Action::setChecked and provide Action::setBlockedChecked instead. --- src/Gui/Action.cpp | 21 +++++++++++++-------- src/Gui/Action.h | 3 ++- src/Gui/Command.cpp | 4 ++-- src/Gui/CommandView.cpp | 20 ++++++++++---------- src/Gui/CommandWindow.cpp | 4 ++-- src/Mod/TechDraw/Gui/CommandDecorate.cpp | 4 ++-- 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index 395095c408..c839412deb 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -137,16 +137,21 @@ void Action::setCheckable(bool check) } } -void Action::setChecked(bool check, bool no_signal) +void Action::setChecked(bool check) { - bool blocked = false; - if (no_signal) { - blocked = _action->blockSignals(true); - } _action->setChecked(check); - if (no_signal) { - _action->blockSignals(blocked); - } +} + +/*! + * \brief Action::setBlockedChecked + * \param check + * Does the same as \ref setChecked but additionally blocks + * any signals. + */ +void Action::setBlockedChecked(bool check) +{ + QSignalBlocker block(_action); + _action->setChecked(check); } bool Action::isChecked() const diff --git a/src/Gui/Action.h b/src/Gui/Action.h index d31c8e4563..62222990fc 100644 --- a/src/Gui/Action.h +++ b/src/Gui/Action.h @@ -57,7 +57,8 @@ public: virtual void setVisible(bool); void setCheckable(bool); - void setChecked (bool, bool no_signal=false); + void setChecked(bool); + void setBlockedChecked(bool); bool isChecked() const; bool isEnabled() const; diff --git a/src/Gui/Command.cpp b/src/Gui/Command.cpp index 4c01a4040c..9bf7ef88c1 100644 --- a/src/Gui/Command.cpp +++ b/src/Gui/Command.cpp @@ -385,7 +385,7 @@ void Command::setupCheckable(int iMsg) { action->setChecked(checked); action->blockSignals(blocked); if(action!=_pcAction->action()) - _pcAction->setChecked(checked,true); + _pcAction->setBlockedChecked(checked); } } @@ -1637,7 +1637,7 @@ Action * PythonGroupCommand::createAction() qtAction->blockSignals(false); }else if(qtAction->isCheckable()){ pcAction->setCheckable(true); - pcAction->setChecked(qtAction->isChecked(),true); + pcAction->setBlockedChecked(qtAction->isChecked()); } } } diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index c0e5c632aa..ccf00cb137 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -3301,7 +3301,7 @@ bool StdCmdSelForward::isActive() DEF_STD_CMD_AC(StdTree##_name) \ void StdTree##_name::activated(int){ \ TreeParams::setDocumentMode(_v);\ - if(_pcAction) _pcAction->setChecked(true,true);\ + if(_pcAction) _pcAction->setBlockedChecked(true);\ }\ Action * StdTree##_name::createAction(void) {\ Action *pcAction = Command::createAction();\ @@ -3314,7 +3314,7 @@ Action * StdTree##_name::createAction(void) {\ bool StdTree##_name::isActive() {\ bool checked = TreeParams::getDocumentMode()==_v;\ if(_pcAction && _pcAction->isChecked()!=checked)\ - _pcAction->setChecked(checked,true);\ + _pcAction->setBlockedChecked(checked);\ return true;\ } @@ -3374,9 +3374,9 @@ DEF_STD_CMD_AC(StdTree##_name) \ void StdTree##_name::activated(int){ \ auto checked = !TreeParams::get##_name();\ TreeParams::set##_name(checked);\ - if(_pcAction) _pcAction->setChecked(checked,true);\ + if(_pcAction) _pcAction->setBlockedChecked(checked);\ }\ -Action * StdTree##_name::createAction(void) {\ +Action * StdTree##_name::createAction() {\ Action *pcAction = Command::createAction();\ pcAction->setCheckable(true);\ pcAction->setIcon(QIcon());\ @@ -3387,7 +3387,7 @@ Action * StdTree##_name::createAction(void) {\ bool StdTree##_name::isActive() {\ bool checked = TreeParams::get##_name();\ if(_pcAction && _pcAction->isChecked()!=checked)\ - _pcAction->setChecked(checked,true);\ + _pcAction->setBlockedChecked(checked);\ return true;\ } @@ -3576,7 +3576,7 @@ void StdCmdSelBoundingBox::activated(int iMsg) if(checked != ViewParams::instance()->getShowSelectionBoundingBox()) { ViewParams::instance()->setShowSelectionBoundingBox(checked); if(_pcAction) - _pcAction->setChecked(checked,true); + _pcAction->setBlockedChecked(checked); } } @@ -3585,7 +3585,7 @@ bool StdCmdSelBoundingBox::isActive() if(_pcAction) { bool checked = _pcAction->isChecked(); if(checked != ViewParams::instance()->getShowSelectionBoundingBox()) - _pcAction->setChecked(!checked,true); + _pcAction->setBlockedChecked(!checked); } return true; } @@ -3819,10 +3819,10 @@ void StdCmdDockOverlayMouseTransparent::activated(int iMsg) bool checked = !OverlayManager::instance()->isMouseTransparent(); OverlayManager::instance()->setMouseTransparent(checked); if(_pcAction) - _pcAction->setChecked(checked,true); + _pcAction->setBlockedChecked(checked); } -Action * StdCmdDockOverlayMouseTransparent::createAction(void) { +Action * StdCmdDockOverlayMouseTransparent::createAction() { Action *pcAction = Command::createAction(); pcAction->setCheckable(true); pcAction->setIcon(QIcon()); @@ -3834,7 +3834,7 @@ Action * StdCmdDockOverlayMouseTransparent::createAction(void) { bool StdCmdDockOverlayMouseTransparent::isActive() { bool checked = OverlayManager::instance()->isMouseTransparent(); if(_pcAction && _pcAction->isChecked()!=checked) - _pcAction->setChecked(checked,true); + _pcAction->setBlockedChecked(checked); return true; } diff --git a/src/Gui/CommandWindow.cpp b/src/Gui/CommandWindow.cpp index b4e925aee5..284299abdb 100644 --- a/src/Gui/CommandWindow.cpp +++ b/src/Gui/CommandWindow.cpp @@ -362,7 +362,7 @@ Action* StdCmdToggleToolBarLock::createAction() Action* action = Command::createAction(); action->setCheckable(true); - action->setChecked(ToolBarManager::getInstance()->areToolBarsLocked(), true); + action->setBlockedChecked(ToolBarManager::getInstance()->areToolBarsLocked()); return action; } @@ -420,7 +420,7 @@ Action * StdCmdStatusBar::createAction() { Action *pcAction = Command::createAction(); pcAction->setCheckable(true); - pcAction->setChecked(false, true); + pcAction->setBlockedChecked(false); auto fsb = new FilterStatusBar(pcAction); getMainWindow()->statusBar()->installEventFilter(fsb); diff --git a/src/Mod/TechDraw/Gui/CommandDecorate.cpp b/src/Mod/TechDraw/Gui/CommandDecorate.cpp index 3c7fb3bfcc..e2c0b35f72 100644 --- a/src/Mod/TechDraw/Gui/CommandDecorate.cpp +++ b/src/Mod/TechDraw/Gui/CommandDecorate.cpp @@ -340,7 +340,7 @@ void CmdTechDrawToggleFrame::activated(int iMsg) Gui::Action *action = this->getAction(); if (action) { - action->setChecked(!vpPage->getFrameState(), true); + action->setBlockedChecked(!vpPage->getFrameState()); } } @@ -358,7 +358,7 @@ bool CmdTechDrawToggleFrame::isActive() Gui::Action* action = this->getAction(); if (action) { - action->setChecked(vpp && !vpp->getFrameState(), true); + action->setBlockedChecked(vpp && !vpp->getFrameState()); } return true; From 30ccf2ab26eb4839399df47bde283da504d6cc87 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 22 Jan 2025 17:00:24 +0100 Subject: [PATCH 02/23] Gui: Use Py::SmartPtr instead of raw pointer Using the raw pointer may result into a crash in case the Python object is accessed by the interpreter after the NavigationStyle has been destroyed. --- src/Gui/Navigation/NavigationStyle.cpp | 16 ++++++++-------- src/Gui/Navigation/NavigationStyle.h | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Gui/Navigation/NavigationStyle.cpp b/src/Gui/Navigation/NavigationStyle.cpp index 38f170877c..ca3d7c3887 100644 --- a/src/Gui/Navigation/NavigationStyle.cpp +++ b/src/Gui/Navigation/NavigationStyle.cpp @@ -319,10 +319,10 @@ NavigationStyle::~NavigationStyle() finalize(); delete this->animator; - if (pythonObject) { + if (!pythonObject.is(nullptr)) { Base::PyGILStateLocker lock; - Py_DECREF(pythonObject); - pythonObject = nullptr; + Base::PyObjectBase* obj = static_cast(pythonObject.ptr()); + obj->setInvalid(); } } @@ -1914,11 +1914,11 @@ void NavigationStyle::openPopupMenu(const SbVec2s& position) PyObject* NavigationStyle::getPyObject() { - if (!pythonObject) - pythonObject = new NavigationStylePy(this); - - Py_INCREF(pythonObject); - return pythonObject; + if (pythonObject.is(nullptr)) { + // ref counter is set to 1 + pythonObject = Py::asObject(new NavigationStylePy(this)); + } + return Py::new_reference_to(pythonObject); } // ---------------------------------------------------------------------------------- diff --git a/src/Gui/Navigation/NavigationStyle.h b/src/Gui/Navigation/NavigationStyle.h index b3000728ab..be54296471 100644 --- a/src/Gui/Navigation/NavigationStyle.h +++ b/src/Gui/Navigation/NavigationStyle.h @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -283,7 +284,7 @@ protected: SbSphereSheetProjector * spinprojector; //@} - PyObject* pythonObject; + Py::SmartPtr pythonObject; private: friend class NavigationAnimator; From c42dfe1ef3cc53f7e7457ec76a43681d72bc6f24 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 23 Jan 2025 23:49:13 +0100 Subject: [PATCH 03/23] Gui: Fix undo/redo behaviour in transformation tool If the user clicks on undo while the transformation dialog is open the currently active transaction will be closed. From now on any change of the placement won't be recorded any more so that e.g. canceling the dialog or an undo after accepting the dialog leads to unexpected behaviour. To fix the issue two new virtual methods onUndo() and onRedo() are added to TaskDialog and reimplemented in TaskTransformDialog. These functions make sure to open a new transaction. This fixes issue 19152 --- src/Gui/TaskTransform.cpp | 32 ++++++++++++++++++++++---------- src/Gui/TaskTransform.h | 5 +++++ src/Gui/TaskView/TaskDialog.cpp | 10 ++++++++++ src/Gui/TaskView/TaskDialog.h | 6 +++++- src/Gui/TaskView/TaskView.cpp | 12 ++++++++---- src/Gui/TaskView/TaskView.h | 2 +- 6 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/Gui/TaskTransform.cpp b/src/Gui/TaskTransform.cpp index fddb2dc46a..2c04213b52 100644 --- a/src/Gui/TaskTransform.cpp +++ b/src/Gui/TaskTransform.cpp @@ -728,16 +728,31 @@ void TaskTransformDialog::open() Gui::TaskView::TaskDialog::open(); - Gui::Application::Instance->activeDocument()->openCommand( - QT_TRANSLATE_NOOP("Command", "Transform")); + openCommand(); +} + +void TaskTransformDialog::openCommand() +{ + if (auto document = vp->getDocument()) { + if (!document->hasPendingCommand()) { + document->openCommand(QT_TRANSLATE_NOOP("Command", "Transform")); + } + } +} + +void TaskTransformDialog::onUndo() +{ + openCommand(); +} + +void TaskTransformDialog::onRedo() +{ + openCommand(); } bool TaskTransformDialog::accept() { - if (auto documentObject = vp->getObject()) { - Gui::Document* document = - Gui::Application::Instance->getDocument(documentObject->getDocument()); - assert(document); + if (auto document = vp->getDocument()) { document->commitCommand(); document->resetEdit(); document->getDocument()->recompute(); @@ -748,10 +763,7 @@ bool TaskTransformDialog::accept() bool TaskTransformDialog::reject() { - if (auto documentObject = vp->getObject()) { - Gui::Document* document = - Gui::Application::Instance->getDocument(documentObject->getDocument()); - assert(document); + if (auto document = vp->getDocument()) { document->abortCommand(); document->resetEdit(); document->getDocument()->recompute(); diff --git a/src/Gui/TaskTransform.h b/src/Gui/TaskTransform.h index 746f12e43b..c8f144e673 100644 --- a/src/Gui/TaskTransform.h +++ b/src/Gui/TaskTransform.h @@ -166,6 +166,11 @@ public: void open() override; bool accept() override; bool reject() override; + void onUndo() override; + void onRedo() override; + +private: + void openCommand(); private: ViewProviderDragger* vp; diff --git a/src/Gui/TaskView/TaskDialog.cpp b/src/Gui/TaskView/TaskDialog.cpp index 5c5bceb088..aba148c36d 100644 --- a/src/Gui/TaskView/TaskDialog.cpp +++ b/src/Gui/TaskView/TaskDialog.cpp @@ -172,6 +172,16 @@ void TaskDialog::helpRequested() } +void TaskDialog::onUndo() +{ + +} + +void TaskDialog::onRedo() +{ + +} + diff --git a/src/Gui/TaskView/TaskDialog.h b/src/Gui/TaskView/TaskDialog.h index 41f6d6e54b..6162efa19d 100644 --- a/src/Gui/TaskView/TaskDialog.h +++ b/src/Gui/TaskView/TaskDialog.h @@ -162,8 +162,12 @@ public: virtual bool accept(); /// is called by the framework if the dialog is rejected (Cancel) virtual bool reject(); - /// is called by the framework if the user press the help button + /// is called by the framework if the user press the help button virtual void helpRequested(); + /// is called by the framework if the user press the undo button + virtual void onUndo(); + /// is called by the framework if the user press the redo button + virtual void onRedo(); void emitDestructionSignal() { Q_EMIT aboutToBeDestroyed(); diff --git a/src/Gui/TaskView/TaskView.cpp b/src/Gui/TaskView/TaskView.cpp index 25ee639aed..37cee0a18d 100644 --- a/src/Gui/TaskView/TaskView.cpp +++ b/src/Gui/TaskView/TaskView.cpp @@ -503,11 +503,15 @@ void TaskView::slotViewClosed(const Gui::MDIView* view) } } -void TaskView::transactionChangeOnDocument(const App::Document& doc) +void TaskView::transactionChangeOnDocument(const App::Document& doc, bool undo) { if (ActiveDialog) { + std::string name = ActiveDialog->getDocumentName(); + if (name == doc.getName()) { + undo ? ActiveDialog->onUndo() : ActiveDialog->onRedo(); + } + if (ActiveDialog->isAutoCloseOnTransactionChange()) { - std::string name = ActiveDialog->getDocumentName(); if (name.empty()) { Base::Console().warning(std::string("TaskView::transactionChangeOnDocument"), "No document name set\n"); @@ -527,12 +531,12 @@ void TaskView::transactionChangeOnDocument(const App::Document& doc) void TaskView::slotUndoDocument(const App::Document& doc) { - transactionChangeOnDocument(doc); + transactionChangeOnDocument(doc, true); } void TaskView::slotRedoDocument(const App::Document& doc) { - transactionChangeOnDocument(doc); + transactionChangeOnDocument(doc, false); } /// @cond DOXERR diff --git a/src/Gui/TaskView/TaskView.h b/src/Gui/TaskView/TaskView.h index f613c4d0d1..bf4537f1b7 100644 --- a/src/Gui/TaskView/TaskView.h +++ b/src/Gui/TaskView/TaskView.h @@ -187,7 +187,7 @@ private: void slotViewClosed(const Gui::MDIView*); void slotUndoDocument(const App::Document&); void slotRedoDocument(const App::Document&); - void transactionChangeOnDocument(const App::Document&); + void transactionChangeOnDocument(const App::Document&, bool undo); protected: void keyPressEvent(QKeyEvent* event) override; From 8b11c0672bd144ee1bb3ae4ee3c359055e0beea9 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 29 Jan 2025 22:18:47 +0100 Subject: [PATCH 04/23] Gui: Adjust transform dragger after undo/redo When performing undo/redo after tansforming an object it flips back to its original position but the dragger does not. This commit set the dragger's placement to the object placement. Fixes issue 18914 --- src/Gui/TaskTransform.cpp | 8 ++++++++ src/Gui/TaskTransform.h | 1 + 2 files changed, 9 insertions(+) diff --git a/src/Gui/TaskTransform.cpp b/src/Gui/TaskTransform.cpp index 2c04213b52..0fd71933d5 100644 --- a/src/Gui/TaskTransform.cpp +++ b/src/Gui/TaskTransform.cpp @@ -740,13 +740,21 @@ void TaskTransformDialog::openCommand() } } +void TaskTransformDialog::updateDraggerPlacement() +{ + const auto placement = vp->getObjectPlacement(); + vp->setDraggerPlacement(placement); +} + void TaskTransformDialog::onUndo() { + updateDraggerPlacement(); openCommand(); } void TaskTransformDialog::onRedo() { + updateDraggerPlacement(); openCommand(); } diff --git a/src/Gui/TaskTransform.h b/src/Gui/TaskTransform.h index c8f144e673..31a05c7dd9 100644 --- a/src/Gui/TaskTransform.h +++ b/src/Gui/TaskTransform.h @@ -171,6 +171,7 @@ public: private: void openCommand(); + void updateDraggerPlacement(); private: ViewProviderDragger* vp; From d969045bd92f892f8ca185b95e56fb346a0b23ec Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 1 Feb 2025 13:51:38 +0100 Subject: [PATCH 05/23] Gui: Fix several methods in Workbench to list all items * Fix Workbench::listToolbars() to also return custom toolbars or added by a manipulator * Fix Workbench::getToolbarItems() to also return custom toolbars or added by a manipulator * Fix Workbench::listMenus() to also return menus added by a manipulator Fixes issue 18647 --- src/Gui/Workbench.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index e0c8a65f05..532b20d7c7 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -463,6 +463,9 @@ void Workbench::removeTaskWatcher() std::list Workbench::listToolbars() const { std::unique_ptr tb(setupToolBars()); + setupCustomToolbars(tb.get(), "Toolbar"); + WorkbenchManipulator::changeToolBars(tb.get()); + std::list bars; QList items = tb->getItems(); for (const auto & item : items) { @@ -474,6 +477,8 @@ std::list Workbench::listToolbars() const std::list>> Workbench::getToolbarItems() const { std::unique_ptr tb(setupToolBars()); + setupCustomToolbars(tb.get(), "Toolbar"); + WorkbenchManipulator::changeToolBars(tb.get()); std::list>> itemsList; QList items = tb->getItems(); @@ -492,6 +497,9 @@ std::list>> Workbench::getToolbarI std::list Workbench::listMenus() const { std::unique_ptr mb(setupMenuBar()); + addPermanentMenuItems(mb.get()); + WorkbenchManipulator::changeMenuBar(mb.get()); + std::list menus; QList items = mb->getItems(); for (const auto & item : items) { From 0cce0f14bf909f863cde1789d02fe2f30687a1e0 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 2 Feb 2025 19:27:30 +0100 Subject: [PATCH 06/23] Gui: Fix DlgPreferencesImp::minimumDialogWidth() to return a reasonable width The current implementation always returns a width that is too small so that a scrollbar is shown. Especially on the General page this is unfortunate as some important controls are truncated or completely hidden. Another problem of this implementation is that when loading a not yet loaded workbench in the 'Available workbenches' page then the computed size will be by far too high leading to a dialog that all the sudden covers most of the screen. Solution: The correct width is the sum of: width of the tree view + width of the biggest page + spacing of the layout This fixes both of the above problems. --- src/Gui/Dialogs/DlgPreferencesImp.cpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Gui/Dialogs/DlgPreferencesImp.cpp b/src/Gui/Dialogs/DlgPreferencesImp.cpp index d451d534e1..d5b539994e 100644 --- a/src/Gui/Dialogs/DlgPreferencesImp.cpp +++ b/src/Gui/Dialogs/DlgPreferencesImp.cpp @@ -365,18 +365,9 @@ int DlgPreferencesImp::minimumDialogWidth(int pageWidth) const { // this is additional safety spacing to ensure that everything fits with scrollbar etc. const auto additionalMargin = style()->pixelMetric(QStyle::PM_ScrollBarExtent) + 8; - - QSize size = ui->groupWidgetStack->sizeHint(); - int diff = pageWidth - size.width(); - int dw = width(); - - if (diff > 0) { - const int offset = 2; - dw += diff + offset; - } - - return dw + additionalMargin; + QSize tree = ui->groupsTreeView->sizeHint(); + return pageWidth + tree.width() + additionalMargin; } void DlgPreferencesImp::updatePageDependentWidgets() @@ -908,7 +899,7 @@ void DlgPreferencesImp::onStackWidgetChange(int index) ui->groupsTreeView->expand(parentItem->index()); parentItem->setExpanded(wasExpanded); } - + ui->groupsTreeView->selectionModel()->select(currentIndex, QItemSelectionModel::ClearAndSelect); } From 05feaea9bdbcf57076df0273f73c09c74f96468a Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 6 Feb 2025 11:44:35 +0100 Subject: [PATCH 07/23] Gui: Do not round color values set in property editor Do not use the number of decimals from the user settings to pass the material to the property as this will cause some unexpected rounding effects. This fixes issue 19048 --- src/Gui/propertyeditor/PropertyItem.cpp | 76 +++++++++++-------------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 7ebe9db4bd..f780e9010d 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -3752,37 +3752,33 @@ void PropertyMaterialItem::setValue(const QVariant& value) auto mat = value.value(); Base::Color dc; dc.setValue(mat.diffuseColor); + uint32_t dcp = dc.getPackedValue(); Base::Color ac; ac.setValue(mat.ambientColor); + uint32_t acp = ac.getPackedValue(); Base::Color sc; sc.setValue(mat.specularColor); + uint32_t scp = sc.getPackedValue(); Base::Color ec; ec.setValue(mat.emissiveColor); + uint32_t ecp = ec.getPackedValue(); float s = mat.shininess; float t = mat.transparency; QString data = QStringLiteral("App.Material(" - "DiffuseColor=(%1,%2,%3)," - "AmbientColor=(%4,%5,%6)," - "SpecularColor=(%7,%8,%9)," - "EmissiveColor=(%10,%11,%12)," - "Shininess=(%13)," - "Transparency=(%14)," - ")") - .arg(dc.r, 0, 'f', decimals()) - .arg(dc.g, 0, 'f', decimals()) - .arg(dc.b, 0, 'f', decimals()) - .arg(ac.r, 0, 'f', decimals()) - .arg(ac.g, 0, 'f', decimals()) - .arg(ac.b, 0, 'f', decimals()) - .arg(sc.r, 0, 'f', decimals()) - .arg(sc.g, 0, 'f', decimals()) - .arg(sc.b, 0, 'f', decimals()) - .arg(ec.r, 0, 'f', decimals()) - .arg(ec.g, 0, 'f', decimals()) - .arg(ec.b, 0, 'f', decimals()) - .arg(s, 0, 'f', decimals()) - .arg(t, 0, 'f', decimals()); + "DiffuseColor = %1," + "AmbientColor = %2," + "SpecularColor = %3," + "EmissiveColor = %4," + "Shininess = %5," + "Transparency = %6," + ")") + .arg(dcp) + .arg(acp) + .arg(scp) + .arg(ecp) + .arg(s, 0, 'f', 10) + .arg(t, 0, 'f', 10); setPropertyValue(data); } @@ -4258,37 +4254,33 @@ void PropertyMaterialListItem::setValue(const QVariant& value) auto mat = list[0].value(); Base::Color dc; dc.setValue(mat.diffuseColor); + uint32_t dcp = dc.getPackedValue(); Base::Color ac; ac.setValue(mat.ambientColor); + uint32_t acp = ac.getPackedValue(); Base::Color sc; sc.setValue(mat.specularColor); + uint32_t scp = sc.getPackedValue(); Base::Color ec; ec.setValue(mat.emissiveColor); + uint32_t ecp = ec.getPackedValue(); float s = mat.shininess; float t = mat.transparency; QString item = QStringLiteral("App.Material(" - "DiffuseColor=(%1,%2,%3)," - "AmbientColor=(%4,%5,%6)," - "SpecularColor=(%7,%8,%9)," - "EmissiveColor=(%10,%11,%12)," - "Shininess=(%13)," - "Transparency=(%14)," - ")") - .arg(dc.r, 0, 'f', decimals()) - .arg(dc.g, 0, 'f', decimals()) - .arg(dc.b, 0, 'f', decimals()) - .arg(ac.r, 0, 'f', decimals()) - .arg(ac.g, 0, 'f', decimals()) - .arg(ac.b, 0, 'f', decimals()) - .arg(sc.r, 0, 'f', decimals()) - .arg(sc.g, 0, 'f', decimals()) - .arg(sc.b, 0, 'f', decimals()) - .arg(ec.r, 0, 'f', decimals()) - .arg(ec.g, 0, 'f', decimals()) - .arg(ec.b, 0, 'f', decimals()) - .arg(s, 0, 'f', decimals()) - .arg(t, 0, 'f', decimals()); + "DiffuseColor = %1," + "AmbientColor = %2," + "SpecularColor = %3," + "EmissiveColor = %4," + "Shininess = %5," + "Transparency = %6," + ")") + .arg(dcp) + .arg(acp) + .arg(scp) + .arg(ecp) + .arg(s, 0, 'f', 10) + .arg(t, 0, 'f', 10); str << item << ")"; setPropertyValue(data); From 55a9123040cfe5c20970f49fe1569fdd277dd6ca Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 7 Feb 2025 19:12:25 +0100 Subject: [PATCH 08/23] Gui: Make AccelLineEdit a subclass of QKeySequenceEdit The class QKeySequenceEdit already implements the relevant functionality and properly handles shortcuts using the Shift key. This fixes issue 19320 --- src/Gui/Dialogs/DlgActionsImp.cpp | 15 ++-- src/Gui/Dialogs/DlgKeyboardImp.cpp | 22 +++--- src/Gui/Dialogs/DlgKeyboardImp.h | 2 +- src/Gui/Widgets.cpp | 110 ++++++----------------------- src/Gui/Widgets.h | 16 ++--- 5 files changed, 49 insertions(+), 116 deletions(-) diff --git a/src/Gui/Dialogs/DlgActionsImp.cpp b/src/Gui/Dialogs/DlgActionsImp.cpp index c0869fd3f7..b0b9c98895 100644 --- a/src/Gui/Dialogs/DlgActionsImp.cpp +++ b/src/Gui/Dialogs/DlgActionsImp.cpp @@ -219,8 +219,8 @@ void DlgCustomActionsImp::onActionListWidgetItemActivated(QTreeWidgetItem* item) ui->actionMenu->setText(QString::fromUtf8(pScript->getMenuText())); ui->actionToolTip->setText(QString::fromUtf8(pScript->getToolTipText())); ui->actionStatus->setText(QString::fromUtf8(pScript->getStatusTip())); - ui->actionAccel->setText( - ShortcutManager::instance()->getShortcut(actionName.constData(), pScript->getAccel())); + ui->actionAccel->setKeySequence(QKeySequence( + ShortcutManager::instance()->getShortcut(actionName.constData(), pScript->getAccel()))); ui->pixmapLabel->clear(); m_sPixmap.clear(); const char* name = pScript->getPixmap(); @@ -290,9 +290,10 @@ void DlgCustomActionsImp::onButtonAddActionClicked() ui->pixmapLabel->clear(); m_sPixmap.clear(); - if (!ui->actionAccel->text().isEmpty()) { + if (!ui->actionAccel->isEmpty()) { + QString text = ui->actionAccel->text(); ShortcutManager::instance()->setShortcut(actionName.constData(), - ui->actionAccel->text().toLatin1().constData()); + text.toLatin1().constData()); } ui->actionAccel->clear(); @@ -353,8 +354,9 @@ void DlgCustomActionsImp::onButtonReplaceActionClicked() ui->pixmapLabel->clear(); m_sPixmap.clear(); - if (!ui->actionAccel->text().isEmpty()) { - macro->setAccel(ui->actionAccel->text().toLatin1()); + if (!ui->actionAccel->isEmpty()) { + QString text = ui->actionAccel->text(); + macro->setAccel(text.toLatin1()); } ui->actionAccel->clear(); @@ -521,7 +523,6 @@ void DlgCustomActionsImp::changeEvent(QEvent* e) ui->retranslateUi(this); ui->actionListWidget->clear(); showActions(); - ui->actionAccel->setText(qApp->translate("Gui::AccelLineEdit", "none")); } QWidget::changeEvent(e); } diff --git a/src/Gui/Dialogs/DlgKeyboardImp.cpp b/src/Gui/Dialogs/DlgKeyboardImp.cpp index 6a90b7950c..c394bff10f 100644 --- a/src/Gui/Dialogs/DlgKeyboardImp.cpp +++ b/src/Gui/Dialogs/DlgKeyboardImp.cpp @@ -139,7 +139,7 @@ void DlgCustomKeyboardImp::setupConnections() this, &DlgCustomKeyboardImp::onButtonResetClicked); connect(ui->buttonResetAll, &QPushButton::clicked, this, &DlgCustomKeyboardImp::onButtonResetAllClicked); - connect(ui->editShortcut, &AccelLineEdit::textChanged, + connect(ui->editShortcut, &AccelLineEdit::keySequenceChanged, this, &DlgCustomKeyboardImp::onEditShortcutTextChanged); // clang-format on } @@ -336,11 +336,11 @@ DlgCustomKeyboardImp::initCommandWidgets(QTreeWidget* commandTreeWidget, auto timer = new QTimer(priorityList); timer->setSingleShot(true); if (currentShortcut) { - QObject::connect(currentShortcut, &QLineEdit::textChanged, timer, [timer]() { + QObject::connect(currentShortcut, &AccelLineEdit::keySequenceChanged, timer, [timer]() { timer->start(200); }); } - QObject::connect(editShortcut, &QLineEdit::textChanged, timer, [timer]() { + QObject::connect(editShortcut, &AccelLineEdit::keySequenceChanged, timer, [timer]() { timer->start(200); }); QObject::connect(ShortcutManager::instance(), @@ -368,10 +368,10 @@ void DlgCustomKeyboardImp::populatePriorityList(QTreeWidget* priorityList, priorityList->clear(); QString sc; - if (!editor->isNone() && editor->text().size()) { + if (!editor->isEmpty()) { sc = editor->text(); } - else if (curShortcut && !curShortcut->isNone()) { + else if (curShortcut && !curShortcut->isEmpty()) { sc = curShortcut->text(); } @@ -466,10 +466,10 @@ void DlgCustomKeyboardImp::onCommandTreeWidgetCurrentItemChanged(QTreeWidgetItem QKeySequence ks2 = QString::fromLatin1(cmd->getAccel()); QKeySequence ks3 = ui->editShortcut->text(); if (ks.isEmpty()) { - ui->accelLineEditShortcut->setText(tr("none")); + ui->accelLineEditShortcut->clear(); } else { - ui->accelLineEditShortcut->setText(ks.toString(QKeySequence::NativeText)); + ui->accelLineEditShortcut->setKeySequence(ks); } ui->buttonAssign->setEnabled(!ui->editShortcut->text().isEmpty() && (ks != ks3)); @@ -500,7 +500,7 @@ void DlgCustomKeyboardImp::setShortcutOfCurrentAction(const QString& accelText) if (!accelText.isEmpty()) { QKeySequence shortcut = accelText; portableText = shortcut.toString(QKeySequence::PortableText); - ui->accelLineEditShortcut->setText(accelText); + ui->accelLineEditShortcut->setKeySequence(shortcut); ui->editShortcut->clear(); } else { @@ -538,7 +538,7 @@ void DlgCustomKeyboardImp::onButtonResetClicked() ShortcutManager::instance()->reset(name); QString txt = ShortcutManager::instance()->getShortcut(name); - ui->accelLineEditShortcut->setText((txt.isEmpty() ? tr("none") : txt)); + ui->accelLineEditShortcut->setKeySequence(QKeySequence(txt)); ui->buttonReset->setEnabled(false); } @@ -550,7 +550,7 @@ void DlgCustomKeyboardImp::onButtonResetAllClicked() } /** Checks for an already occupied shortcut. */ -void DlgCustomKeyboardImp::onEditShortcutTextChanged(const QString&) +void DlgCustomKeyboardImp::onEditShortcutTextChanged(const QKeySequence&) { QTreeWidgetItem* item = ui->commandTreeWidget->currentItem(); if (item) { @@ -560,7 +560,7 @@ void DlgCustomKeyboardImp::onEditShortcutTextChanged(const QString&) CommandManager& cCmdMgr = Application::Instance->commandManager(); Command* cmd = cCmdMgr.getCommandByName(name.constData()); - if (!ui->editShortcut->isNone()) { + if (!ui->editShortcut->isEmpty()) { ui->buttonAssign->setEnabled(true); } else { diff --git a/src/Gui/Dialogs/DlgKeyboardImp.h b/src/Gui/Dialogs/DlgKeyboardImp.h index 1cdc954439..62bae929eb 100644 --- a/src/Gui/Dialogs/DlgKeyboardImp.h +++ b/src/Gui/Dialogs/DlgKeyboardImp.h @@ -108,7 +108,7 @@ protected: void onButtonClearClicked(); void onButtonResetClicked(); void onButtonResetAllClicked(); - void onEditShortcutTextChanged(const QString&); + void onEditShortcutTextChanged(const QKeySequence&); protected Q_SLOTS: void onAddMacroAction(const QByteArray&) override; diff --git a/src/Gui/Widgets.cpp b/src/Gui/Widgets.cpp index a112f5ba42..0c4854d85e 100644 --- a/src/Gui/Widgets.cpp +++ b/src/Gui/Widgets.cpp @@ -358,105 +358,39 @@ void ActionSelector::onDownButtonClicked() /** * Constructs a line edit with no text. - * The \a parent argument is sent to the QLineEdit constructor. + * The \a parent argument is sent to the QKeySequenceEdit constructor. */ -AccelLineEdit::AccelLineEdit ( QWidget * parent ) - : QLineEdit(parent) +AccelLineEdit::AccelLineEdit(QWidget* parent) + : QKeySequenceEdit(parent) { - setPlaceholderText(tr("Press a keyboard shortcut")); - setClearButtonEnabled(true); - keyPressedCount = 0; + if (auto le = findChild()) { + le->setClearButtonEnabled(true); + } } -bool AccelLineEdit::isNone() const +AccelLineEdit::AccelLineEdit(const QKeySequence& keySequence, QWidget* parent) + : QKeySequenceEdit(keySequence, parent) { - return text().isEmpty(); + if (auto le = findChild()) { + le->setClearButtonEnabled(true); + } } -/** - * Checks which keys are pressed and show it as text. - */ -void AccelLineEdit::keyPressEvent (QKeyEvent * e) +void AccelLineEdit::setReadOnly(bool value) { - if (isReadOnly()) { - QLineEdit::keyPressEvent(e); - return; + if (auto le = findChild()) { + le->setReadOnly(value); } +} - QString txtLine = text(); +bool AccelLineEdit::isEmpty() const +{ + return keySequence().isEmpty(); +} - int key = e->key(); - Qt::KeyboardModifiers state = e->modifiers(); - - // Backspace clears the shortcut if text is present, else sets Backspace as shortcut. - // If a modifier is pressed without any other key, return. - // AltGr is not a modifier but doesn't have a QString representation. - switch(key) { - case Qt::Key_Backspace: - case Qt::Key_Delete: - if (state == Qt::NoModifier) { - keyPressedCount = 0; - if (isNone()) { - QKeySequence ks(key); - setText(ks.toString(QKeySequence::NativeText)); - } - else { - clear(); - } - } - case Qt::Key_Control: - case Qt::Key_Shift: - case Qt::Key_Alt: - case Qt::Key_Meta: - case Qt::Key_AltGr: - return; - default: - break; - } - - if (txtLine.isEmpty()) { - // Text maybe cleared by QLineEdit's built in clear button - keyPressedCount = 0; - } else { - // 4 keys are allowed for QShortcut - switch (keyPressedCount) { - case 4: - keyPressedCount = 0; - txtLine.clear(); - break; - case 0: - txtLine.clear(); - break; - default: - txtLine += QStringLiteral(","); - break; - } - } - - // Handles modifiers applying a mask. - if ((state & Qt::ControlModifier) == Qt::ControlModifier) { - QKeySequence ks(Qt::CTRL); - txtLine += ks.toString(QKeySequence::NativeText); - } - if ((state & Qt::AltModifier) == Qt::AltModifier) { - QKeySequence ks(Qt::ALT); - txtLine += ks.toString(QKeySequence::NativeText); - } - if ((state & Qt::ShiftModifier) == Qt::ShiftModifier) { - QKeySequence ks(Qt::SHIFT); - txtLine += ks.toString(QKeySequence::NativeText); - } - if ((state & Qt::MetaModifier) == Qt::MetaModifier) { - QKeySequence ks(Qt::META); - txtLine += ks.toString(QKeySequence::NativeText); - } - - // Handles normal keys - QKeySequence ks(key); - txtLine += ks.toString(QKeySequence::NativeText); - - setText(txtLine); - keyPressedCount++; +QString AccelLineEdit::text() const +{ + return keySequence().toString(QKeySequence::NativeText); } // ------------------------------------------------------------------------------ diff --git a/src/Gui/Widgets.h b/src/Gui/Widgets.h index 01f4ab54f9..f086c28f91 100644 --- a/src/Gui/Widgets.h +++ b/src/Gui/Widgets.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -132,19 +133,16 @@ private: * The AccelLineEdit class provides a lineedit to specify shortcuts. * \author Werner Mayer */ -class GuiExport AccelLineEdit : public QLineEdit +class GuiExport AccelLineEdit : public QKeySequenceEdit { Q_OBJECT public: - AccelLineEdit(QWidget * parent=nullptr); - bool isNone() const; - -protected: - void keyPressEvent(QKeyEvent * e) override; - -private: - int keyPressedCount; + explicit AccelLineEdit(QWidget* parent = nullptr); + explicit AccelLineEdit(const QKeySequence& keySequence, QWidget* parent = nullptr); + void setReadOnly(bool value); + bool isEmpty() const; + QString text() const; }; // ------------------------------------------------------------------------------ From c21b330a9857895d48d188fc663069219f11fc6b Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 10 Feb 2025 14:38:55 +0100 Subject: [PATCH 09/23] Gui: Fix command StdCmdProperties The command is only able to show the property view in case it's invisible. But it fails to raise the widget. And if in the preferences 'Combined' mode is set it fails completely. Solution: The method DockWindowManager::activate already does everything what's needed. Now the command only must be changed to access the right docked widget. --- src/Gui/CommandDoc.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Gui/CommandDoc.cpp b/src/Gui/CommandDoc.cpp index 3fc5199174..fc1fba78a3 100644 --- a/src/Gui/CommandDoc.cpp +++ b/src/Gui/CommandDoc.cpp @@ -1752,12 +1752,14 @@ StdCmdProperties::StdCmdProperties() void StdCmdProperties::activated(int iMsg) { Q_UNUSED(iMsg); - QWidget* propertyView = Gui::DockWindowManager::instance()->getDockWindow("Property view"); - if (propertyView) { - QWidget* parent = propertyView->parentWidget(); - if (parent && !parent->isVisible()) { - parent->show(); - } + auto dw = Gui::DockWindowManager::instance(); + if (auto propertyView = dw->getDockWindow("Property view")) { + dw->activate(propertyView); + return; + } + if (auto comboView = dw->getDockWindow("Model")) { + dw->activate(comboView); + return; } } From 59717fc6076f314991a4c9b7fcc33c2ad730eedc Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 15 Feb 2025 01:12:31 +0100 Subject: [PATCH 10/23] Gui: Fix crash when trying to transform link object This is a regression caused by PR 17564. ViewProviderDragger has the member 'forwardedViewProvider' that is used to handle the editing by the parent object. This means that inside ViewProviderLink::startEditing 'transformDragger' can be null but this isn't checked so that accessing the member causes a segmentation fault or a failing assert in debug mode, respectively. Solution: Make sure that transformDragger is not null before accessing it. This fixes 19542. --- src/Gui/ViewProviderLink.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Gui/ViewProviderLink.cpp b/src/Gui/ViewProviderLink.cpp index 265d77ef56..28f8f71f6c 100644 --- a/src/Gui/ViewProviderLink.cpp +++ b/src/Gui/ViewProviderLink.cpp @@ -2793,11 +2793,13 @@ ViewProvider *ViewProviderLink::startEditing(int mode) { } if (auto result = inherited::startEditing(mode)) { - transformDragger->addStartCallback(dragStartCallback, this); - transformDragger->addFinishCallback(dragFinishCallback, this); - transformDragger->addMotionCallback(dragMotionCallback, this); + if (transformDragger.get()) { + transformDragger->addStartCallback(dragStartCallback, this); + transformDragger->addFinishCallback(dragFinishCallback, this); + transformDragger->addMotionCallback(dragMotionCallback, this); - setDraggerPlacement(dragCtx->initialPlacement); + setDraggerPlacement(dragCtx->initialPlacement); + } return result; } From efe02a8675c307a67e9029b1bf6e429d25557495 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 20 Feb 2025 22:33:04 +0100 Subject: [PATCH 11/23] Gui: Fix stackoverflow when loading corrupted file If an object has a link to itself it may cause a stackoverflow in several cases: * If the method claimChildren3D() returns a list containing the object of the view provider then Document::handleChildren3D() will add a SoGroup to itself as a child. This will result into a stackoverflow as soon as an action traverses the scene. * If the method claimChildren() returns a list containing the object of the view provider then DocumentItem::createNewItem() causes an infinite loop with DocumentItem::populateItem() Solution: * Inside Document::handleChildren3D() avoid to add a SoGroup to itself * In this specific case fix ViewProviderCoordinateSystem::claimChildren() to avoid a cyclic dependency Hint: Since PR 18126 FreeCAD is vulnerable for this problem. This fixes issue 19682 --- src/Gui/Document.cpp | 43 ++++++++++++++++++------ src/Gui/ViewProviderCoordinateSystem.cpp | 13 +++++-- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index 7182eaae31..e703c41e35 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -2719,21 +2719,42 @@ void Document::handleChildren3D(ViewProvider* viewProvider, bool deleting) if(!deleting) { for (const auto & it : children) { - ViewProvider* ChildViewProvider = getViewProvider(it); - if (ChildViewProvider) { - auto itOld = oldChildren.find(static_cast(ChildViewProvider)); + if (auto ChildViewProvider = dynamic_cast(getViewProvider(it))) { + auto itOld = oldChildren.find(ChildViewProvider); if(itOld!=oldChildren.end()) oldChildren.erase(itOld); - SoSeparator* childRootNode = ChildViewProvider->getRoot(); - childGroup->addChild(childRootNode); + if (SoSeparator* childRootNode = ChildViewProvider->getRoot()) { + if (childRootNode == childGroup) { + Base::Console().warning("Document::handleChildren3D: Do not add " + "group of '%s' to itself\n", + it->getNameInDocument()); + } + else if (childGroup) { + childGroup->addChild(childRootNode); + } + } - SoSeparator* childFrontNode = ChildViewProvider->getFrontRoot(); - if (frontGroup && childFrontNode) - frontGroup->addChild(childFrontNode); + if (SoSeparator* childFrontNode = ChildViewProvider->getFrontRoot()) { + if (childFrontNode == frontGroup) { + Base::Console().warning("Document::handleChildren3D: Do not add " + "foreground group of '%s' to itself\n", + it->getNameInDocument()); + } + else if (frontGroup) { + frontGroup->addChild(childFrontNode); + } + } - SoSeparator* childBackNode = ChildViewProvider->getBackRoot(); - if (backGroup && childBackNode) - backGroup->addChild(childBackNode); + if (SoSeparator* childBackNode = ChildViewProvider->getBackRoot()) { + if (childBackNode == backGroup) { + Base::Console().warning("Document::handleChildren3D: Do not add " + "background group of '%s' to itself\n", + it->getNameInDocument()); + } + else if (backGroup) { + backGroup->addChild(childBackNode); + } + } // cycling to all views of the document to remove the viewprovider from the viewer itself for (Gui::BaseView* vIt : d->baseViews) { diff --git a/src/Gui/ViewProviderCoordinateSystem.cpp b/src/Gui/ViewProviderCoordinateSystem.cpp index 2689e1031b..3d46ff658f 100644 --- a/src/Gui/ViewProviderCoordinateSystem.cpp +++ b/src/Gui/ViewProviderCoordinateSystem.cpp @@ -72,11 +72,18 @@ ViewProviderCoordinateSystem::~ViewProviderCoordinateSystem() { std::vector ViewProviderCoordinateSystem::claimChildren() const { - return static_cast( getObject() )->OriginFeatures.getValues(); + auto obj = getObject(); + std::vector childs = obj->OriginFeatures.getValues(); + auto it = std::find(childs.begin(), childs.end(), obj); + if (it != childs.end()) { + childs.erase(it); + } + return childs; } -std::vector ViewProviderCoordinateSystem::claimChildren3D() const { - return claimChildren (); +std::vector ViewProviderCoordinateSystem::claimChildren3D() const +{ + return claimChildren(); } void ViewProviderCoordinateSystem::attach(App::DocumentObject* pcObject) From 87fca2c943c125e79216b36a2ba595a4175ceae4 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 27 Feb 2025 23:17:44 +0100 Subject: [PATCH 12/23] Gui: Use QDialogButtonBox in DlgExpressionInput This way the order of buttons is more consistent than using separate QPushButtons. This fixes issue 19902 --- src/Gui/Dialogs/DlgExpressionInput.cpp | 32 +++++++++-------- src/Gui/Dialogs/DlgExpressionInput.h | 2 ++ src/Gui/Dialogs/DlgExpressionInput.ui | 50 ++++++-------------------- 3 files changed, 30 insertions(+), 54 deletions(-) diff --git a/src/Gui/Dialogs/DlgExpressionInput.cpp b/src/Gui/Dialogs/DlgExpressionInput.cpp index 336bbbd2e5..337cc24f02 100644 --- a/src/Gui/Dialogs/DlgExpressionInput.cpp +++ b/src/Gui/Dialogs/DlgExpressionInput.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #endif @@ -66,13 +67,16 @@ DlgExpressionInput::DlgExpressionInput(const App::ObjectIdentifier & _path, // Setup UI ui->setupUi(this); + okBtn = ui->buttonBox->button(QDialogButtonBox::Ok); + discardBtn = ui->buttonBox->button(QDialogButtonBox::Reset); + discardBtn->setToolTip(tr("Revert to last calculated value (as constant)")); initializeVarSets(); // Connect signal(s) connect(ui->expression, &ExpressionLineEdit::textChanged, this, &DlgExpressionInput::textChanged); - connect(ui->discardBtn, &QPushButton::clicked, + connect(discardBtn, &QPushButton::clicked, this, &DlgExpressionInput::setDiscarded); if (expression) { @@ -280,11 +284,11 @@ void DlgExpressionInput::checkExpression(const QString& text) std::unique_ptr result(expr->eval()); expression = expr; - ui->okBtn->setEnabled(true); + okBtn->setEnabled(true); ui->msg->clear(); //set default palette as we may have read text right now - ui->msg->setPalette(ui->okBtn->palette()); + ui->msg->setPalette(okBtn->palette()); auto * n = freecad_cast(result.get()); if (n) { @@ -325,12 +329,12 @@ static const bool NO_CHECK_EXPR = false; void DlgExpressionInput::textChanged(const QString &text) { if (text.isEmpty()) { - ui->okBtn->setDisabled(true); - ui->discardBtn->setDefault(true); + okBtn->setDisabled(true); + discardBtn->setDefault(true); return; } - ui->okBtn->setDefault(true); + okBtn->setDefault(true); try { //resize the input field according to text size @@ -357,7 +361,7 @@ void DlgExpressionInput::textChanged(const QString &text) QPalette p(ui->msg->palette()); p.setColor(QPalette::WindowText, Qt::red); ui->msg->setPalette(p); - ui->okBtn->setDisabled(true); + okBtn->setDisabled(true); } } @@ -614,7 +618,7 @@ void DlgExpressionInput::setupVarSets() ui->comboBoxVarSet->setModel(treeWidget->model()); ui->comboBoxVarSet->setView(treeWidget.get()); - ui->okBtn->setEnabled(false); + okBtn->setEnabled(false); } std::string DlgExpressionInput::getType() @@ -629,7 +633,7 @@ void DlgExpressionInput::onCheckVarSets(int state) { setupVarSets(); } else { - ui->okBtn->setEnabled(true); // normal expression + okBtn->setEnabled(true); // normal expression } } @@ -719,13 +723,13 @@ void DlgExpressionInput::updateVarSetInfo(bool checkExpr) QString nameGroup = ui->lineEditGroup->text(); if (reportGroup(nameGroup)) { // needed to report something about the group, so disable the button - ui->okBtn->setEnabled(false); + okBtn->setEnabled(false); return; } if (reportName(selected)) { // needed to report something about the name, so disable the button - ui->okBtn->setEnabled(false); + okBtn->setEnabled(false); return; } @@ -744,15 +748,15 @@ void DlgExpressionInput::updateVarSetInfo(bool checkExpr) // We have to check the text of the expression as well try { checkExpression(ui->expression->text()); - ui->okBtn->setEnabled(true); + okBtn->setEnabled(true); } catch (Base::Exception&) { - ui->okBtn->setDisabled(true); + okBtn->setDisabled(true); } } } else { - ui->okBtn->setEnabled(false); + okBtn->setEnabled(false); reportVarSetInfo("Please select a variable set."); } } diff --git a/src/Gui/Dialogs/DlgExpressionInput.h b/src/Gui/Dialogs/DlgExpressionInput.h index 7cb5c8dd92..7f6f20220a 100644 --- a/src/Gui/Dialogs/DlgExpressionInput.h +++ b/src/Gui/Dialogs/DlgExpressionInput.h @@ -120,6 +120,8 @@ private: static bool varSetsVisible; std::unique_ptr treeWidget; + QPushButton* okBtn = nullptr; + QPushButton* discardBtn = nullptr; }; } diff --git a/src/Gui/Dialogs/DlgExpressionInput.ui b/src/Gui/Dialogs/DlgExpressionInput.ui index 8b07b3812a..6b33d3cd25 100644 --- a/src/Gui/Dialogs/DlgExpressionInput.ui +++ b/src/Gui/Dialogs/DlgExpressionInput.ui @@ -249,42 +249,6 @@ - - - - 2 - - - - - Revert to last calculated value (as constant) - - - &Clear - - - true - - - false - - - - - - - &OK - - - true - - - true - - - - - @@ -298,6 +262,13 @@ + + + + QDialogButtonBox::Reset|QDialogButtonBox::Ok + + + @@ -324,15 +295,14 @@ expression - okBtn - discardBtn + buttonBox checkBoxVarSets - okBtn - clicked() + buttonBox + accepted() DlgExpressionInput accept() From a0899cc65dcafa179bff3016c5c11e1c441566b6 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 10 Mar 2025 18:05:52 +0100 Subject: [PATCH 13/23] Gui: Improve document recovery When checking for recovery files also check for validity of the actual project file. In case it's broken but has a never date then still process the recovery file. This is done to reduce the chance of data loss as described in issue 18044 --- src/Gui/DocumentRecovery.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Gui/DocumentRecovery.cpp b/src/Gui/DocumentRecovery.cpp index 539cddecbe..642f730332 100644 --- a/src/Gui/DocumentRecovery.cpp +++ b/src/Gui/DocumentRecovery.cpp @@ -49,6 +49,7 @@ #include #include +#include #include #include #include @@ -164,6 +165,7 @@ public: QList recoveryInfo; Info getRecoveryInfo(const QFileInfo&) const; + bool isValidProject(const QFileInfo&) const; void writeRecoveryInfo(const Info&) const; XmlConfig readXmlFile(const QString& fn) const; }; @@ -433,7 +435,7 @@ DocumentRecoveryPrivate::Info DocumentRecoveryPrivate::getRecoveryInfo(const QFi if (info.status == DocumentRecoveryPrivate::Created) { // compare the modification dates QFileInfo fileInfo(info.fileName); - if (!info.fileName.isEmpty() && fileInfo.exists()) { + if (!info.fileName.isEmpty() && isValidProject(fileInfo)) { QDateTime dateRecv = QFileInfo(file).lastModified(); QDateTime dateProj = fileInfo.lastModified(); if (dateRecv < dateProj) { @@ -449,6 +451,16 @@ DocumentRecoveryPrivate::Info DocumentRecoveryPrivate::getRecoveryInfo(const QFi return info; } +bool DocumentRecoveryPrivate::isValidProject(const QFileInfo& fi) const +{ + if (!fi.exists()) { + return false; + } + + App::ProjectFile project(fi.absoluteFilePath().toStdString()); + return project.loadDocument(); +} + DocumentRecoveryPrivate::XmlConfig DocumentRecoveryPrivate::readXmlFile(const QString& fn) const { DocumentRecoveryPrivate::XmlConfig cfg; From da6bafe2466addda4fb6369db698e99c85237c8e Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 14 Mar 2025 16:31:27 +0100 Subject: [PATCH 14/23] Gui: Fix crash in Command::keySequenceToAccel If FreeCAD is loaded without GUI it isn't allowed to access the QKeySequence class as it will cause a crash. So, it checks beforehand if QApplication::instance() is null. This fixes issue 16407 --- src/Gui/Command.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Gui/Command.cpp b/src/Gui/Command.cpp index 9bf7ef88c1..0bc31de89c 100644 --- a/src/Gui/Command.cpp +++ b/src/Gui/Command.cpp @@ -899,8 +899,14 @@ const char* Command::keySequenceToAccel(int sk) const static StringMap strings; auto i = strings.find(sk); - if (i != strings.end()) + if (i != strings.end()) { return i->second.c_str(); + } + + // In case FreeCAD is loaded without GUI (issue 16407) + if (!QApplication::instance()) { + return ""; + } auto type = static_cast(sk); QKeySequence ks(type); From dec434e15376c4461ccf94f3969c1f8a657fc8ec Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 16 Mar 2025 14:03:27 +0100 Subject: [PATCH 15/23] Gui: Handle exception when trying to start editing an object --- src/Gui/Document.cpp | 1 + src/Gui/ViewProvider.cpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index e703c41e35..50d97b3b17 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -648,6 +648,7 @@ bool Document::trySetEdit(Gui::ViewProvider* p, int ModNum, const char *subname) Application::Instance->setEditDocument(this); if (!d->tryStartEditing(vp, obj, _subname.c_str(), ModNum)) { + d->setDocumentNameOfTaskDialog(getDocument()); return false; } diff --git a/src/Gui/ViewProvider.cpp b/src/Gui/ViewProvider.cpp index 82823721a2..1f04a65a10 100644 --- a/src/Gui/ViewProvider.cpp +++ b/src/Gui/ViewProvider.cpp @@ -137,9 +137,14 @@ ViewProvider::~ViewProvider() ViewProvider *ViewProvider::startEditing(int ModNum) { - if(setEdit(ModNum)) { - _iEditMode = ModNum; - return this; + try { + if (setEdit(ModNum)) { + _iEditMode = ModNum; + return this; + } + } + catch (const Base::Exception& e) { + e.reportException(); } return nullptr; } From 9497ddf5e15d1f2878c7e0b7829a79e1d59f5b9a Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 16 Mar 2025 23:25:28 +0100 Subject: [PATCH 16/23] Gui: Replace calls of FCMD_SET_EDIT with Gui::cmdSetEdit --- src/Gui/Command.h | 2 +- src/Mod/Part/Gui/ViewProvider.cpp | 4 ++-- src/Mod/PartDesign/Gui/ViewProvider.cpp | 4 ++-- src/Mod/PartDesign/Gui/ViewProviderBase.cpp | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Gui/Command.h b/src/Gui/Command.h index cce11e8594..50809a8f45 100644 --- a/src/Gui/Command.h +++ b/src/Gui/Command.h @@ -181,7 +181,7 @@ if(__obj && __obj->isAttachedToDocument()) {\ Gui::Command::doCommand(Gui::Command::Gui,\ "Gui.ActiveDocument.setEdit(App.getDocument('%s').getObject('%s'), %i)",\ - __obj->getDocument()->getName(), __obj->getNameInDocument(), Gui::Application::Instance->getUserEditMode());\ + __obj->getDocument()->getName(), __obj->getNameInDocument(), 0);\ }\ }while(0) diff --git a/src/Mod/Part/Gui/ViewProvider.cpp b/src/Mod/Part/Gui/ViewProvider.cpp index d15bdf649e..81b2862b50 100644 --- a/src/Mod/Part/Gui/ViewProvider.cpp +++ b/src/Mod/Part/Gui/ViewProvider.cpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include "ViewProvider.h" @@ -50,7 +50,7 @@ bool ViewProviderPart::doubleClicked() try { QString text = QObject::tr("Edit %1").arg(QString::fromUtf8(getObject()->Label.getValue())); Gui::Command::openCommand(text.toUtf8()); - FCMD_SET_EDIT(pcObject); + Gui::cmdSetEdit(pcObject); return true; } catch (const Base::Exception& e) { diff --git a/src/Mod/PartDesign/Gui/ViewProvider.cpp b/src/Mod/PartDesign/Gui/ViewProvider.cpp index b435320fea..58ac6fce77 100644 --- a/src/Mod/PartDesign/Gui/ViewProvider.cpp +++ b/src/Mod/PartDesign/Gui/ViewProvider.cpp @@ -34,7 +34,7 @@ #include #include #include -#include +#include #include #include #include @@ -63,7 +63,7 @@ bool ViewProvider::doubleClicked() try { QString text = QObject::tr("Edit %1").arg(QString::fromUtf8(getObject()->Label.getValue())); Gui::Command::openCommand(text.toUtf8()); - FCMD_SET_EDIT(pcObject); + Gui::cmdSetEdit(pcObject); } catch (const Base::Exception&) { Gui::Command::abortCommand(); diff --git a/src/Mod/PartDesign/Gui/ViewProviderBase.cpp b/src/Mod/PartDesign/Gui/ViewProviderBase.cpp index 58e9bad695..81b53450ce 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderBase.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderBase.cpp @@ -24,7 +24,7 @@ #include "PreCompiled.h" #include -#include +#include #include #include "ViewProviderBase.h" @@ -54,7 +54,7 @@ bool ViewProviderBase::doubleClicked() std::string Msg("Edit "); Msg += base->Label.getValue(); Gui::Command::openCommand(Msg.c_str()); - FCMD_SET_EDIT(base); + Gui::cmdSetEdit(base); } catch (const Base::Exception&) { Gui::Command::abortCommand(); From 3888899e7cc44605f712d2b58d2b99dbcc455d0d Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 17 Mar 2025 14:18:48 +0100 Subject: [PATCH 17/23] Gui: avoid code duplication in PythonEditor --- src/Gui/PythonEditor.cpp | 22 +------------- src/Gui/TextEdit.cpp | 65 ++++++++++++++++++++-------------------- src/Gui/TextEdit.h | 4 +++ 3 files changed, 38 insertions(+), 53 deletions(-) diff --git a/src/Gui/PythonEditor.cpp b/src/Gui/PythonEditor.cpp index a76c1eeba7..4154cc6a4d 100644 --- a/src/Gui/PythonEditor.cpp +++ b/src/Gui/PythonEditor.cpp @@ -243,27 +243,7 @@ void PythonEditor::onComment() void PythonEditor::onUncomment() { - QTextCursor cursor = textCursor(); - int selStart = cursor.selectionStart(); - int selEnd = cursor.selectionEnd(); - QTextBlock block; - cursor.beginEditBlock(); - for (block = document()->begin(); block.isValid(); block = block.next()) { - int pos = block.position(); - int off = block.length()-1; - // at least one char of the block is part of the selection - if ( pos >= selStart || pos+off >= selStart) { - if ( pos+1 > selEnd ) - break; // end of selection reached - if (block.text().startsWith(QLatin1String("#"))) { - cursor.setPosition(block.position()); - cursor.deleteChar(); - selEnd--; - } - } - } - - cursor.endEditBlock(); + remove(QStringLiteral("#")); } void PythonEditor::onExecuteInConsole() diff --git a/src/Gui/TextEdit.cpp b/src/Gui/TextEdit.cpp index 0ad823dce7..c11d081101 100644 --- a/src/Gui/TextEdit.cpp +++ b/src/Gui/TextEdit.cpp @@ -519,6 +519,34 @@ void PythonTextEditor::prepend(const QString& str) cursor.endEditBlock(); } +void PythonTextEditor::remove(const QString& str) +{ + QTextCursor cursor = textCursor(); + int selStart = cursor.selectionStart(); + int selEnd = cursor.selectionEnd(); + QTextBlock block; + cursor.beginEditBlock(); + for (block = document()->begin(); block.isValid(); block = block.next()) { + int pos = block.position(); + int off = block.length()-1; + // at least one char of the block is part of the selection + if ( pos >= selStart || pos+off >= selStart) { + if ( pos+1 > selEnd ) + break; // end of selection reached + QString text = block.text(); + if (text.startsWith(str)) { + cursor.setPosition(block.position()); + for (int i = 0; i < str.length(); i++) { + cursor.deleteChar(); + selEnd--; + } + } + } + } + + cursor.endEditBlock(); +} + void PythonTextEditor::keyPressEvent (QKeyEvent * e) { if ( e->key() == Qt::Key_Tab ) { @@ -547,40 +575,13 @@ void PythonTextEditor::keyPressEvent (QKeyEvent * e) // If some text is selected we remove a leading tab or // spaces from each selected block ParameterGrp::handle hPrefGrp = getWindowParameter(); + bool space = hPrefGrp->GetBool("Spaces", true); int indent = hPrefGrp->GetInt( "IndentSize", 4 ); + QString ch = space ? QString(indent, QLatin1Char(' ')) + : QStringLiteral("\t"); - int selStart = cursor.selectionStart(); - int selEnd = cursor.selectionEnd(); - QTextBlock block; - cursor.beginEditBlock(); - for (block = document()->begin(); block.isValid(); block = block.next()) { - int pos = block.position(); - int off = block.length()-1; - // at least one char of the block is part of the selection - if ( pos >= selStart || pos+off >= selStart) { - if ( pos+1 > selEnd ) - break; // end of selection reached - // if possible remove one tab or several spaces - QString text = block.text(); - if (text.startsWith(QLatin1String("\t"))) { - cursor.setPosition(block.position()); - cursor.deleteChar(); - selEnd--; - } - else { - cursor.setPosition(block.position()); - for (int i=0; i Date: Fri, 4 Apr 2025 20:04:29 +0200 Subject: [PATCH 18/23] Gui: Fix crash in ~PythonBaseWorkbench with Python 3.12 --- src/Gui/Workbench.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 532b20d7c7..f44bf2ea0e 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -1057,6 +1057,7 @@ PythonBaseWorkbench::~PythonBaseWorkbench() delete _toolBar; delete _commandBar; if (_workbenchPy) { + Base::PyGILStateLocker lock; _workbenchPy->setInvalid(); _workbenchPy->DecRef(); } From 4cfc77d9ee914a77245a135c9505627bbdf7942e Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 25 Apr 2025 22:23:06 +0200 Subject: [PATCH 19/23] Gui: Move define HAS_QTBUG_129596 to separate header file --- cMake/FreeCAD_Helpers/SetupQt.cmake | 1 + src/Gui/Application.cpp | 1 + src/Gui/Application.h | 4 ---- src/Gui/MainWindow.cpp | 1 + src/QtWidgets.h.cmake | 11 +++++++++++ 5 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 src/QtWidgets.h.cmake diff --git a/cMake/FreeCAD_Helpers/SetupQt.cmake b/cMake/FreeCAD_Helpers/SetupQt.cmake index c3dc0eeedd..eba599830a 100644 --- a/cMake/FreeCAD_Helpers/SetupQt.cmake +++ b/cMake/FreeCAD_Helpers/SetupQt.cmake @@ -78,6 +78,7 @@ else() endif() configure_file(${CMAKE_SOURCE_DIR}/src/QtCore.h.cmake ${CMAKE_BINARY_DIR}/src/QtCore.h) +configure_file(${CMAKE_SOURCE_DIR}/src/QtWidgets.h.cmake ${CMAKE_BINARY_DIR}/src/QtWidgets.h) function(qt_find_and_add_translation _qm_files _tr_dir _qm_dir) file(GLOB _ts_files ${_tr_dir}) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 35a37e9e64..7b99d70022 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -137,6 +137,7 @@ #include "WorkbenchManipulator.h" #include "WidgetFactory.h" #include "3Dconnexion/navlib/NavlibInterface.h" +#include "QtWidgets.h" #ifdef BUILD_TRACY_FRAME_PROFILER #include diff --git a/src/Gui/Application.h b/src/Gui/Application.h index 3cece3a157..596fc32dae 100644 --- a/src/Gui/Application.h +++ b/src/Gui/Application.h @@ -30,10 +30,6 @@ #include -#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) && QT_VERSION < QT_VERSION_CHECK(6,8,1) -# define HAS_QTBUG_129596 -#endif - class QCloseEvent; class SoNode; class NavlibInterface; diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index ab2cf7184c..0a76f6dba0 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -120,6 +120,7 @@ #include "Dialogs/DlgObjectSelection.h" #include +#include "QtWidgets.h" FC_LOG_LEVEL_INIT("MainWindow",false,true,true) diff --git a/src/QtWidgets.h.cmake b/src/QtWidgets.h.cmake new file mode 100644 index 0000000000..c7d64c59f1 --- /dev/null +++ b/src/QtWidgets.h.cmake @@ -0,0 +1,11 @@ +#ifndef FREECAD_QTWIDGETS_H +#define FREECAD_QTWIDGETS_H + +#include + + +#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) && QT_VERSION < QT_VERSION_CHECK(6,8,1) +# define HAS_QTBUG_129596 +#endif + +#endif // FREECAD_QTWIDGETS_H From 8d3e38fb4ab670adbd6b357896c89b24a1472cb3 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 26 Apr 2025 13:26:08 +0200 Subject: [PATCH 20/23] Gui: Improve auto-saving Handle possibly raised exceptions in RecoveryRunnable::run(). Since the run() method is executed within the context of a worker thread all exceptions must be handled before returning to Qt Concurrent as otherwise the application will be terminated. For testing purposes load the corrupted project file from this forum thread https://forum.freecad.org/viewtopic.php?p=823608#p823608 and wait for the auto-saving. --- src/Gui/AutoSaver.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Gui/AutoSaver.cpp b/src/Gui/AutoSaver.cpp index 6228221486..62e76b1001 100644 --- a/src/Gui/AutoSaver.cpp +++ b/src/Gui/AutoSaver.cpp @@ -344,17 +344,28 @@ public: } void run() override { - prop->SaveDocFile(writer); - writer.close(); + try { + prop->SaveDocFile(writer); + writer.close(); - // We could have renamed the file in this thread. However, there is - // still chance of crash when we deleted the original and before rename - // the new file. So we ask the main thread to do it. There is still - // possibility of crash caused by thread other than the main, but - // that's the best we can do for now. - QMetaObject::invokeMethod(AutoSaver::instance(), "renameFile", - Qt::QueuedConnection, Q_ARG(QString,dirName) - ,Q_ARG(QString,fileName),Q_ARG(QString,tmpName)); + // We could have renamed the file in this thread. However, there is + // still chance of crash when we deleted the original and before rename + // the new file. So we ask the main thread to do it. There is still + // possibility of crash caused by thread other than the main, but + // that's the best we can do for now. + QMetaObject::invokeMethod(AutoSaver::instance(), "renameFile", + Qt::QueuedConnection, Q_ARG(QString,dirName) + ,Q_ARG(QString,fileName),Q_ARG(QString,tmpName)); + } + catch (const Base::Exception& e) { + Base::Console().warning("Exception in auto-saving: %s\n", e.what()); + } + catch (const std::exception& e) { + Base::Console().warning("C++ exception in auto-saving: %s\n", e.what()); + } + catch (...) { + Base::Console().warning("Unknown exception in auto-saving\n"); + } } private: From a124ce4f8fc93d8281b5fef3b66ff74d993f8997 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 27 Apr 2025 19:46:05 +0200 Subject: [PATCH 21/23] Gui: In DockWindowItems::addDockWidget replace the booleans with the enum class DockWindowOptions --- src/Gui/DockWindowManager.cpp | 6 +++--- src/Gui/DockWindowManager.h | 17 ++++++++++++++++- src/Gui/Workbench.cpp | 18 +++++++++--------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/Gui/DockWindowManager.cpp b/src/Gui/DockWindowManager.cpp index 69eb85409c..9a65987ec6 100644 --- a/src/Gui/DockWindowManager.cpp +++ b/src/Gui/DockWindowManager.cpp @@ -49,13 +49,13 @@ DockWindowItems::DockWindowItems() = default; DockWindowItems::~DockWindowItems() = default; -void DockWindowItems::addDockWidget(const char* name, Qt::DockWidgetArea pos, bool visibility, bool tabbed) +void DockWindowItems::addDockWidget(const char* name, Qt::DockWidgetArea pos, DockWindowOptions option) { DockWindowItem item; item.name = QString::fromUtf8(name); item.pos = pos; - item.visibility = visibility; - item.tabbed = tabbed; + item.visibility = option.testFlag(DockWindowOption::Visible); + item.tabbed = option.testFlag(DockWindowOption::HiddenTabbed); _items << item; } diff --git a/src/Gui/DockWindowManager.h b/src/Gui/DockWindowManager.h index c6ceb9440c..19cf6fe230 100644 --- a/src/Gui/DockWindowManager.h +++ b/src/Gui/DockWindowManager.h @@ -24,6 +24,7 @@ #define GUI_DOCKWINDOWMANAGER_H #include +#include #include class QDockWidget; @@ -31,6 +32,18 @@ class QWidget; namespace Gui { +enum class DockWindowOption +{ + // clang-format off + Hidden = 0, + Visible = 1, + HiddenTabbed = 2, + VisibleTabbed = 3 + // clang-format on +}; + +using DockWindowOptions = Base::Flags; + struct DockWindowItem { QString name; Qt::DockWidgetArea pos; @@ -44,7 +57,7 @@ public: DockWindowItems(); ~DockWindowItems(); - void addDockWidget(const char* name, Qt::DockWidgetArea pos, bool visibility, bool tabbed); + void addDockWidget(const char* name, Qt::DockWidgetArea pos, DockWindowOptions option); void setDockingArea(const char* name, Qt::DockWidgetArea pos); void setVisibility(const char* name, bool v); void setVisibility(bool v); @@ -122,4 +135,6 @@ private: } // namespace Gui +ENABLE_BITMASK_OPERATORS(Gui::DockWindowOption) + #endif // GUI_DOCKWINDOWMANAGER_H diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index f44bf2ea0e..6aecbb3b49 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -860,13 +860,13 @@ ToolBarItem* StdWorkbench::setupCommandBars() const DockWindowItems* StdWorkbench::setupDockWindows() const { auto root = new DockWindowItems(); - root->addDockWidget("Std_TreeView", Qt::LeftDockWidgetArea, true, false); - root->addDockWidget("Std_PropertyView", Qt::LeftDockWidgetArea, true, false); - root->addDockWidget("Std_SelectionView", Qt::LeftDockWidgetArea, false, false); - root->addDockWidget("Std_ComboView", Qt::LeftDockWidgetArea, true, true); - root->addDockWidget("Std_TaskView", Qt::LeftDockWidgetArea, true, true); - root->addDockWidget("Std_ReportView", Qt::BottomDockWidgetArea, false, true); - root->addDockWidget("Std_PythonView", Qt::BottomDockWidgetArea, false, true); + root->addDockWidget("Std_TreeView", Qt::LeftDockWidgetArea, Gui::DockWindowOption::Visible); + root->addDockWidget("Std_PropertyView", Qt::LeftDockWidgetArea, Gui::DockWindowOption::Visible); + root->addDockWidget("Std_SelectionView", Qt::LeftDockWidgetArea, Gui::DockWindowOption::Hidden); + root->addDockWidget("Std_ComboView", Qt::LeftDockWidgetArea, Gui::DockWindowOption::VisibleTabbed); + root->addDockWidget("Std_TaskView", Qt::LeftDockWidgetArea, Gui::DockWindowOption::VisibleTabbed); + root->addDockWidget("Std_ReportView", Qt::BottomDockWidgetArea, Gui::DockWindowOption::HiddenTabbed); + root->addDockWidget("Std_PythonView", Qt::BottomDockWidgetArea, Gui::DockWindowOption::HiddenTabbed); //Dagview through parameter. ParameterGrp::handle group = App::GetApplication().GetUserParameter(). @@ -874,7 +874,7 @@ DockWindowItems* StdWorkbench::setupDockWindows() const bool enabled = group->GetBool("Enabled", false); if (enabled) { - root->addDockWidget("Std_DAGView", Qt::RightDockWidgetArea, false, false); + root->addDockWidget("Std_DAGView", Qt::RightDockWidgetArea, Gui::DockWindowOption::Hidden); } return root; @@ -995,7 +995,7 @@ ToolBarItem* NoneWorkbench::setupCommandBars() const DockWindowItems* NoneWorkbench::setupDockWindows() const { auto root = new DockWindowItems(); - root->addDockWidget("Std_ReportView", Qt::BottomDockWidgetArea, true, false); + root->addDockWidget("Std_ReportView", Qt::BottomDockWidgetArea, Gui::DockWindowOption::Visible); return root; } From 2aba143df1f27526445bddb6b23fac350cd5a4c0 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 3 May 2025 22:34:22 +0200 Subject: [PATCH 22/23] Gui: Improve DlgAddPropertyVarSet * Give user a hint about the reason of an invalid name. Fixes issue 21111 * Fix isTypeValid() to check for valid property type --- src/Gui/Dialogs/DlgAddPropertyVarSet.cpp | 38 ++++++++++++++++++++++-- src/Gui/Dialogs/DlgAddPropertyVarSet.h | 1 + src/Gui/Dialogs/DlgAddPropertyVarSet.ui | 9 +++++- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/Gui/Dialogs/DlgAddPropertyVarSet.cpp b/src/Gui/Dialogs/DlgAddPropertyVarSet.cpp index 94011897c6..3ef09b990d 100644 --- a/src/Gui/Dialogs/DlgAddPropertyVarSet.cpp +++ b/src/Gui/Dialogs/DlgAddPropertyVarSet.cpp @@ -371,8 +371,7 @@ bool DlgAddPropertyVarSet::isGroupValid() bool DlgAddPropertyVarSet::isTypeValid() { std::string type = ui->comboBoxType->currentText().toStdString(); - - return !Base::Type::fromName(type.c_str()).isBad(); + return Base::Type::fromName(type.c_str()).isDerivedFrom(App::Property::getClassTypeId()); } bool DlgAddPropertyVarSet::areFieldsValid() @@ -380,9 +379,41 @@ bool DlgAddPropertyVarSet::areFieldsValid() return isNameValid() && isGroupValid() && isTypeValid(); } -void DlgAddPropertyVarSet::onTextFieldChanged([[maybe_unused]] const QString& text) +void DlgAddPropertyVarSet::onTextFieldChanged([[maybe_unused]]const QString& text) { setOkEnabled(areFieldsValid()); + showStatusMessage(); +} + +void DlgAddPropertyVarSet::showStatusMessage() +{ + QString error; + QString text = ui->lineEditName->text(); + std::string name = text.toStdString(); + + if (!isGroupValid()) { + error = tr("Invalid group name"); + } + else if (!isTypeValid()) { + error = tr("Invalid type name"); + } + else if (name.empty()) { + error.clear(); + } + else if (name != Base::Tools::getIdentifier(name)) { + error = tr("Invalid property name '%1'").arg(text); + } + else if (propertyExists(name)) { + error = tr("Property '%1' already exists").arg(text); + } + else if (App::ExpressionParser::isTokenAConstant(name)) { + error = tr("'%1' is a constant").arg(text); + } + else if (App::ExpressionParser::isTokenAUnit(name)) { + error = tr("'%1' is a unit").arg(text); + } + + ui->labelError->setText(error); } void DlgAddPropertyVarSet::removeEditor() @@ -409,6 +440,7 @@ void DlgAddPropertyVarSet::onTypeChanged(const QString& text) } setOkEnabled(areFieldsValid()); + showStatusMessage(); } void DlgAddPropertyVarSet::changeEvent(QEvent* e) diff --git a/src/Gui/Dialogs/DlgAddPropertyVarSet.h b/src/Gui/Dialogs/DlgAddPropertyVarSet.h index d0b539ae24..8e19901f3b 100644 --- a/src/Gui/Dialogs/DlgAddPropertyVarSet.h +++ b/src/Gui/Dialogs/DlgAddPropertyVarSet.h @@ -121,6 +121,7 @@ private: bool areFieldsValid(); void onTextFieldChanged(const QString& text); + void showStatusMessage(); void removeEditor(); void onTypeChanged(const QString& text); diff --git a/src/Gui/Dialogs/DlgAddPropertyVarSet.ui b/src/Gui/Dialogs/DlgAddPropertyVarSet.ui index d46bc7ba31..651c8fc452 100644 --- a/src/Gui/Dialogs/DlgAddPropertyVarSet.ui +++ b/src/Gui/Dialogs/DlgAddPropertyVarSet.ui @@ -7,7 +7,7 @@ 0 0 418 - 223 + 234 @@ -70,6 +70,13 @@ + + + + + + + Qt::Horizontal From 25691764f99742fc2efed99b064f83f1ba70cc8c Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 7 May 2025 19:26:44 +0200 Subject: [PATCH 23/23] Gui: Show date & time in locale specific format --- src/Gui/Dialogs/DlgProjectInformationImp.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Gui/Dialogs/DlgProjectInformationImp.cpp b/src/Gui/Dialogs/DlgProjectInformationImp.cpp index 465c04106c..a242448d27 100644 --- a/src/Gui/Dialogs/DlgProjectInformationImp.cpp +++ b/src/Gui/Dialogs/DlgProjectInformationImp.cpp @@ -24,7 +24,9 @@ #ifndef _PreComp_ #include #include +#include #include +#include #include #endif @@ -71,15 +73,25 @@ DlgProjectInformationImp::DlgProjectInformationImp(App::Document* doc, , _doc(doc) , ui(new Ui_DlgProjectInformation) { + auto convertISODate = [](const char* isoDate) { + auto str = QString::fromUtf8(isoDate); + QDateTime dt = QDateTime::fromString(str, Qt::DateFormat::ISODate); + if (dt.isNull()) { + return str; + } + + QLocale loc = QLocale::system(); + return loc.toString(dt); + }; ui->setupUi(this); ui->lineEditName->setText(QString::fromUtf8(doc->Label.getValue())); ui->lineEditPath->setText(QString::fromUtf8(doc->FileName.getValue())); ui->lineEditUuid->setText(QString::fromUtf8(doc->Uid.getValueStr().c_str())); ui->lineEditProgramVersion->setText(QString::fromUtf8(doc->getProgramVersion())); ui->lineEditCreator->setText(QString::fromUtf8(doc->CreatedBy.getValue())); - ui->lineEditDate->setText(QString::fromUtf8(doc->CreationDate.getValue())); + ui->lineEditDate->setText(convertISODate(doc->CreationDate.getValue())); ui->lineEditLastMod->setText(QString::fromUtf8(doc->LastModifiedBy.getValue())); - ui->lineEditLastModDate->setText(QString::fromUtf8(doc->LastModifiedDate.getValue())); + ui->lineEditLastModDate->setText(convertISODate(doc->LastModifiedDate.getValue())); ui->lineEditCompany->setText(QString::fromUtf8(doc->Company.getValue())); // Load comboBox with unit systems