diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 3bf63d9514..fb6969fe7c 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1166,6 +1166,7 @@ SOURCE_GROUP("View3D\\Inventor" FILES ${Inventor_SRCS}) # The widget sources SET(Widget_CPP_SRCS + ComboLinks.cpp FileDialog.cpp MainWindow.cpp MainWindowPy.cpp @@ -1189,6 +1190,7 @@ SET(Widget_CPP_SRCS FontScaledSVG.cpp ) SET(Widget_HPP_SRCS + ComboLinks.h FileDialog.h MainWindow.h MainWindowPy.h diff --git a/src/Gui/ComboLinks.cpp b/src/Gui/ComboLinks.cpp new file mode 100644 index 0000000000..7e1f3ebf40 --- /dev/null +++ b/src/Gui/ComboLinks.cpp @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 AstoCAD * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#include "ComboLinks.h" + +#ifndef _PreComp_ +#include +#include +#include +#endif + +#include +#include + +namespace Gui { + + ComboLinks::ComboLinks(QComboBox& combo) + : _combo(&combo) + { + if (_combo) { + _combo->clear(); + } + } + + ComboLinks::~ComboLinks() + { + clear(); // Deletes owned pointers in linksInList + _combo = nullptr; // Don't delete, not owned + } + + void ComboLinks::setCombo(QComboBox& combo) + { + clear(); // Clear old state if any + _combo = &combo; + if (_combo) { + _combo->clear(); + } + } + + int ComboLinks::addLink(const App::PropertyLinkSub& lnk, const QString& itemText, int userData) + { + if (!_combo) { + return -1; + } + int newIndex = _combo->count(); + int finalUserData = (userData == -1) ? newIndex : userData; + + // Store link internally (create a copy) + auto* newLink = new App::PropertyLinkSub(); + newLink->Paste(lnk); + linksInList.push_back(newLink); + + // Add to combo box + _combo->addItem(itemText, QVariant(finalUserData)); + + // Track document context from the first valid object link + if (!doc && newLink->getValue()) { + doc = newLink->getValue()->getDocument(); + } + return newIndex; + } + + int ComboLinks::addLink(App::DocumentObject* linkObj, const std::string& linkSubname, const QString& itemText, int userData) + { + App::PropertyLinkSub lnk; + std::vector sub = { linkSubname }; + // Handle empty subname correctly + if (linkSubname.empty()) { + sub.clear(); + } + lnk.setValue(linkObj, sub); + return addLink(lnk, itemText, userData); + } + + int ComboLinks::addLinkBefore(const App::PropertyLinkSub& lnk, const QString& itemText, int targetUserData, int userData) + { + if (!_combo) { + return -1; + } + + int insertPos = -1; + for (int i = 0; i < _combo->count(); ++i) { + if (_combo->itemData(i).toInt() == targetUserData) { + insertPos = i; + break; + } + } + + // Store link internally (create a copy) + auto* newLink = new App::PropertyLinkSub(); + newLink->Paste(lnk); + + int finalUserData = (userData == -1) ? ((insertPos == -1) ? count() : insertPos) : userData; + + if (insertPos != -1) { + linksInList.insert(linksInList.begin() + insertPos, newLink); + _combo->insertItem(insertPos, itemText, QVariant(finalUserData)); + } + else { + // Target not found, append to end + insertPos = _combo->count(); + linksInList.push_back(newLink); + _combo->addItem(itemText, QVariant(finalUserData)); + } + + // Update user data for subsequent items if default (-1) was used and inserting + if (userData == -1 && insertPos != -1 && insertPos < count() - 1) { + for (int i = insertPos + 1; i < count(); ++i) { + if (_combo->itemData(i).toInt() == i - 1) { // Check if it was using default index + _combo->setItemData(i, QVariant(i)); + } + } + } + + + // Track document context + if (!doc && newLink->getValue()) { + doc = newLink->getValue()->getDocument(); + } + return insertPos; + } + + + void ComboLinks::clear() + { + if (_combo) { + // Block signals while clearing to prevent issues if connected elsewhere + bool wasBlocked = _combo->signalsBlocked(); + _combo->blockSignals(true); + _combo->clear(); + _combo->blockSignals(wasBlocked); + } + for (App::PropertyLinkSub* linkPtr : linksInList) { + delete linkPtr; // Delete the objects pointed to + } + linksInList.clear(); // Clear the vector itself + doc = nullptr; // Reset document context + } + + App::PropertyLinkSub& ComboLinks::getLink(int index) const + { + if (index < 0 || static_cast(index) >= linksInList.size()) { + throw Base::IndexError("ComboLinks::getLink: Index out of range"); + } + App::PropertyLinkSub* linkPtr = linksInList[static_cast(index)]; + // Perform validity check only if we have a document context and a linked object + if (doc && linkPtr->getValue() && !(doc->isIn(linkPtr->getValue()))) { + throw Base::ValueError("Linked object is not in the document; it may have been deleted"); + } + return *linkPtr; + } + + App::PropertyLinkSub& ComboLinks::getCurrentLink() const + { + assert(_combo); + return getLink(_combo->currentIndex()); + + } + + int ComboLinks::getUserData(int index) const + { + if (!_combo || index < 0 || index >= _combo->count()) { + return -1; // Indicate invalid index or no combo + } + return _combo->itemData(index).toInt(); + } + + int ComboLinks::getCurrentUserData() const + { + if (!_combo) { + return -1; + } + return _combo->currentData().toInt(); + } + + + int ComboLinks::setCurrentLink(const App::PropertyLinkSub& lnk) + { + if (!_combo) { + return -1; + } + for (size_t i = 0; i < linksInList.size(); ++i) { + const App::PropertyLinkSub& it = *(linksInList[i]); + // Compare object pointer and sub-values vector + if (lnk.getValue() == it.getValue() && lnk.getSubValues() == it.getSubValues()) { + bool wasBlocked = _combo->signalsBlocked(); + _combo->blockSignals(true); + _combo->setCurrentIndex(static_cast(i)); + _combo->blockSignals(wasBlocked); + return static_cast(i); + } + } + return -1; // Not found + } + + int ComboLinks::count() const + { + return _combo ? _combo->count() : 0; + } + +} // namespace Gui diff --git a/src/Gui/ComboLinks.h b/src/Gui/ComboLinks.h new file mode 100644 index 0000000000..cbc8939a68 --- /dev/null +++ b/src/Gui/ComboLinks.h @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 AstoCAD * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef GUI_COMBOLINKS_H +#define GUI_COMBOLINKS_H + +#include +#include + +#include // For PropertyLinkSub +#include // For exceptions + +// Forward declarations +class QComboBox; +class QString; +namespace App { + class Document; + class DocumentObject; +} + +namespace Gui { + + /** + * @brief The ComboLinks class is a helper class that binds to a QComboBox and + * provides an interface to add App::PropertyLinkSub items, retrieve links, + * and select items by link value. + */ + class GuiExport ComboLinks + { + public: + /** + * @brief Constructor. Binds to an existing QComboBox. + * @param combo The combo box to manage. It will be cleared upon binding. + * Do not add/remove items directly to/from the combo after binding. + */ + explicit ComboLinks(QComboBox& combo); + + /** + * @brief Default constructor. Use setCombo() later. + */ + ComboLinks() = default; + + /** + * @brief Destructor. Clears internal resources. + */ + ~ComboLinks(); + + // Disable copy/move semantics for simplicity with pointer management + ComboLinks(const ComboLinks&) = delete; + ComboLinks& operator=(const ComboLinks&) = delete; + ComboLinks(ComboLinks&&) = delete; + ComboLinks& operator=(ComboLinks&&) = delete; + + /** + * @brief Binds the helper to a QComboBox. Clears the combo box. + * @param combo The combo box to manage. + */ + void setCombo(QComboBox& combo); + + /** + * @brief Adds an item to the combo box associated with a PropertyLinkSub. + * @param lnk The link data. Can point to nullptr (e.g., for "Select reference..."). + * @param itemText The text to display in the combo box. + * @param userData Optional integer data associated with the item (default: item index). + * @return The index of the added item. + */ + int addLink(const App::PropertyLinkSub& lnk, const QString& itemText, int userData = -1); + + /** + * @brief Adds an item to the combo box associated with specific object and subname. + * @param linkObj The document object (can be nullptr). + * @param linkSubname The sub-element name (e.g., "Edge1", "Face2", "X_Axis"). + * @param itemText The text to display in the combo box. + * @param userData Optional integer data associated with the item (default: item index). + * @return The index of the added item. + */ + int addLink(App::DocumentObject* linkObj, const std::string& linkSubname, const QString& itemText, int userData = -1); + + + /** + * @brief Adds an item *before* an item identified by its user data. + * Useful for inserting custom items before a standard item like "Select reference...". + * If targetUserData is not found, appends to the end. + * + * @param lnk The link data. + * @param itemText The text to display. + * @param targetUserData The user data of the item to insert before. + * @param userData Optional integer data for the new item (default: item index). + * @return The index of the inserted item. + */ + int addLinkBefore(const App::PropertyLinkSub& lnk, const QString& itemText, int targetUserData, int userData = -1); + + + /** + * @brief Clears all items from the combo box and the internal list. + */ + void clear(); + + /** + * @brief Gets the PropertyLinkSub associated with the item at the given index. + * @param index The index of the item. + * @return A reference to the stored PropertyLinkSub. + * @throws Base::IndexError if index is out of range. + * @throws Base::ValueError if the linked object exists but is not in the tracked document. + */ + App::PropertyLinkSub& getLink(int index) const; + + /** + * @brief Gets the PropertyLinkSub associated with the currently selected item. + * @return A reference to the stored PropertyLinkSub. + * @throws Base::IndexError if no item is selected or combo is invalid. + * @throws Base::ValueError if linked object validity check fails. + */ + App::PropertyLinkSub& getCurrentLink() const; + + /** + * @brief Gets the user data associated with the item at the given index. + * @param index The index of the item. + * @return The user data. Returns -1 if index is invalid. + */ + int getUserData(int index) const; + + /** + * @brief Gets the user data associated with the currently selected item. + * @return The user data. Returns -1 if no item is selected or combo is invalid. + */ + int getCurrentUserData() const; + + /** + * @brief Selects the item whose PropertyLinkSub matches the provided link. + * Blocks combo box signals during the operation. + * @param lnk The link to match. + * @return The index of the selected item, or -1 if no match is found. + */ + int setCurrentLink(const App::PropertyLinkSub& lnk); + + /** + * @brief Gets the number of items in the combo box. + * @return Item count. + */ + int count() const; + + + private: + QComboBox* _combo = nullptr; + App::Document* doc = nullptr; // Document context for validation + std::vector linksInList; // Owned pointers + }; + +} // namespace Gui + +#endif // GUI_COMBOLINKS_H \ No newline at end of file diff --git a/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.h b/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.h index 33f3a7b22f..81b38aaa82 100644 --- a/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.h +++ b/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.h @@ -92,7 +92,7 @@ private: std::unique_ptr ui; QTimer* updateViewTimer = nullptr; - ComboLinks dirLinks; + Gui::ComboLinks dirLinks; }; diff --git a/src/Mod/PartDesign/Gui/TaskMirroredParameters.h b/src/Mod/PartDesign/Gui/TaskMirroredParameters.h index bb393833cf..78f2dade02 100644 --- a/src/Mod/PartDesign/Gui/TaskMirroredParameters.h +++ b/src/Mod/PartDesign/Gui/TaskMirroredParameters.h @@ -73,7 +73,7 @@ private: void getMirrorPlane(App::DocumentObject*& obj, std::vector& sub) const; private: - ComboLinks planeLinks; + Gui::ComboLinks planeLinks; std::unique_ptr ui; }; diff --git a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h index f91a1a717e..b961fd67b9 100644 --- a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h @@ -93,7 +93,7 @@ private: std::unique_ptr ui; QTimer* updateViewTimer = nullptr; - ComboLinks axesLinks; + Gui::ComboLinks axesLinks; }; diff --git a/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp b/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp index 52f01dffe1..02d16efb38 100644 --- a/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp @@ -638,84 +638,3 @@ bool TaskDlgTransformedParameters::reject() #include "moc_TaskTransformedParameters.cpp" - - -ComboLinks::ComboLinks(QComboBox& combo) - : _combo(&combo) -{ - _combo->clear(); -} - -int ComboLinks::addLink(const App::PropertyLinkSub& lnk, QString const& itemText) -{ - if (!_combo) { - return 0; - } - _combo->addItem(itemText); - this->linksInList.push_back(new App::PropertyLinkSub()); - App::PropertyLinkSub& newitem = *(linksInList[linksInList.size() - 1]); - newitem.Paste(lnk); - if (newitem.getValue() && !this->doc) { - this->doc = newitem.getValue()->getDocument(); - } - return linksInList.size() - 1; -} - -int ComboLinks::addLink(App::DocumentObject* linkObj, - std::string const& linkSubname, - QString const& itemText) -{ - if (!_combo) { - return 0; - } - _combo->addItem(itemText); - this->linksInList.push_back(new App::PropertyLinkSub()); - App::PropertyLinkSub& newitem = *(linksInList[linksInList.size() - 1]); - newitem.setValue(linkObj, std::vector(1, linkSubname)); - if (newitem.getValue() && !this->doc) { - this->doc = newitem.getValue()->getDocument(); - } - return linksInList.size() - 1; -} - -void ComboLinks::clear() -{ - for (size_t i = 0; i < this->linksInList.size(); i++) { - delete linksInList[i]; - } - if (this->_combo) { - _combo->clear(); - } -} - -App::PropertyLinkSub& ComboLinks::getLink(int index) const -{ - if (index < 0 || index > static_cast(linksInList.size()) - 1) { - throw Base::IndexError("ComboLinks::getLink:Index out of range"); - } - if (linksInList[index]->getValue() && doc && !(doc->isIn(linksInList[index]->getValue()))) { - throw Base::ValueError("Linked object is not in the document; it may have been deleted"); - } - return *(linksInList[index]); -} - -App::PropertyLinkSub& ComboLinks::getCurrentLink() const -{ - assert(_combo); - return getLink(_combo->currentIndex()); -} - -int ComboLinks::setCurrentLink(const App::PropertyLinkSub& lnk) -{ - for (size_t i = 0; i < linksInList.size(); i++) { - App::PropertyLinkSub& it = *(linksInList[i]); - if (lnk.getValue() == it.getValue() && lnk.getSubValues() == it.getSubValues()) { - bool wasBlocked = _combo->signalsBlocked(); - _combo->blockSignals(true); - _combo->setCurrentIndex(i); - _combo->blockSignals(wasBlocked); - return i; - } - } - return -1; -} diff --git a/src/Mod/PartDesign/Gui/TaskTransformedParameters.h b/src/Mod/PartDesign/Gui/TaskTransformedParameters.h index 3b5e0cdf90..23aaa39110 100644 --- a/src/Mod/PartDesign/Gui/TaskTransformedParameters.h +++ b/src/Mod/PartDesign/Gui/TaskTransformedParameters.h @@ -24,8 +24,7 @@ #ifndef GUI_TASKVIEW_TaskTransformedParameters_H #define GUI_TASKVIEW_TaskTransformedParameters_H -#include - +#include #include #include #include @@ -55,78 +54,6 @@ namespace PartDesignGui class TaskMultiTransformParameters; -/** - * @brief The ComboLinks class is a helper class that binds to a combo box and - * provides an interface to add links, retrieve links and select items by link - * value - */ -class ComboLinks -{ -public: - /** - * @brief ComboLinks constructor. - * @param combo. It will be cleared as soon as it is bound. Don't add or - * remove items from the combo directly, otherwise internal tracking list - * will go out of sync, and crashes may result. - */ - explicit ComboLinks(QComboBox& combo); - ComboLinks() = default; - - void setCombo(QComboBox& combo) - { - assert(!_combo); - this->_combo = &combo; - _combo->clear(); - } - - /** - * @brief addLink adds an item to the combo. Doesn't check for duplicates. - * @param lnk can be a link to NULL, which is usually used for special item "Select Reference" - * @param itemText - * @return - */ - int addLink(const App::PropertyLinkSub& lnk, QString const& itemText); - int addLink(App::DocumentObject* linkObj, std::string const& linkSubname, QString const& itemText); - void clear(); - App::PropertyLinkSub& getLink(int index) const; - - /** - * @brief getCurrentLink - * @return the link corresponding to the selected item. May be null link, - * which is usually used to indicate a "Select reference..." special item. - * Otherwise, the link is automatically tested for validity (oif an object - * doesn't exist in the document, an exception will be thrown.) - */ - App::PropertyLinkSub& getCurrentLink() const; - - /** - * @brief setCurrentLink selects the item with the link that matches the - * argument. If there is no such link in the list, -1 is returned and - * selected item is not changed. Signals from combo are blocked in this - * function. - * @param lnk - * @return the index of an item that was selected, -1 if link is not in the list yet. - */ - int setCurrentLink(const App::PropertyLinkSub& lnk); - - QComboBox& combo() const - { - assert(_combo); - return *_combo; - } - - ~ComboLinks() - { - _combo = nullptr; - clear(); - } - -private: - QComboBox* _combo = nullptr; - App::Document* doc = nullptr; - std::vector linksInList; -}; - /** The transformed subclasses will be used in two different modes: 1. As a stand-alone feature