From ba34c6fb879716a06b7ebf2652aa70404fab9273 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 7 Mar 2023 23:21:05 -0600 Subject: [PATCH 01/12] App: Add IndexedName class Ported from development/toponaming. --- src/App/CMakeLists.txt | 2 + src/App/IndexedName.cpp | 127 ++++++++++++++++++++++++++++ src/App/IndexedName.h | 181 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 src/App/IndexedName.cpp create mode 100644 src/App/IndexedName.h diff --git a/src/App/CMakeLists.txt b/src/App/CMakeLists.txt index 02ebe92de2..918ced0a6f 100644 --- a/src/App/CMakeLists.txt +++ b/src/App/CMakeLists.txt @@ -261,6 +261,7 @@ SET(FreeCADApp_CPP_SRCS ComplexGeoData.cpp ComplexGeoDataPyImp.cpp Enumeration.cpp + IndexedName.cpp Material.cpp MaterialPyImp.cpp Metadata.cpp @@ -277,6 +278,7 @@ SET(FreeCADApp_HPP_SRCS ColorModel.h ComplexGeoData.h Enumeration.h + IndexedName.h Material.h Metadata.h ) diff --git a/src/App/IndexedName.cpp b/src/App/IndexedName.cpp new file mode 100644 index 0000000000..e4bc448d4d --- /dev/null +++ b/src/App/IndexedName.cpp @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/**************************************************************************** + * Copyright (c) 2022 Zheng, Lei (realthunder) * + * * + * 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" + +#ifndef _PreComp_ +# include +# include +#endif + +#include + +#include "IndexedName.h" + +using namespace Data; + +struct ByteArray +{ + ByteArray(const QByteArray& b) + :bytes(b) + {} + + ByteArray(const ByteArray& other) + :bytes(other.bytes) + {} + + ByteArray(ByteArray&& other) + :bytes(std::move(other.bytes)) + {} + + void mutate() const + { + QByteArray copy; + copy.append(bytes.constData(), bytes.size()); + bytes = copy; + } + + bool operator==(const ByteArray& other) const { + return bytes == other.bytes; + } + + mutable QByteArray bytes; +}; + +struct ByteArrayHasher +{ + std::size_t operator()(const ByteArray& bytes) const + { + return qHash(bytes.bytes); + } + + std::size_t operator()(const QByteArray& bytes) const + { + return qHash(bytes); + } +}; + +void IndexedName::set( + const char* name, + int len, + const std::vector& types, + bool allowOthers) +{ + static std::unordered_set NameSet; + + if (len < 0) + len = static_cast(std::strlen(name)); + int i; + for (i = len - 1; i >= 0; --i) { + if (name[i] < '0' || name[i]>'9') + break; + } + ++i; + this->index = std::atoi(name + i); + + for (int j = 0; j < i; ++j) { + if (name[j] == '_' + || (name[j] >= 'a' && name[j] <= 'z') + || (name[j] >= 'A' && name[j] <= 'Z')) + continue; + this->type = ""; + return; + } + + for (const char* type : types) { + int j = 0; + for (const char* n = name, *t = type; *n; ++n) { + if (*n != *t || j >= i) + break; + ++j; + ++t; + if (!*t) { + this->type = type; + return; + } + } + } + + if (allowOthers) { + auto res = NameSet.insert(QByteArray::fromRawData(name, i)); + if (res.second) + res.first->mutate(); + this->type = res.first->bytes.constData(); + } + else + this->type = ""; +} diff --git a/src/App/IndexedName.h b/src/App/IndexedName.h new file mode 100644 index 0000000000..3e64882934 --- /dev/null +++ b/src/App/IndexedName.h @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/**************************************************************************** + * Copyright (c) 2022 Zheng, Lei (realthunder) * + * * + * 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 _IndexedName_h_ +#define _IndexedName_h_ + +#include +#include +#include +#include +#include + +#include + +#include "FCGlobal.h" + + +namespace Data +{ + +class AppExport IndexedName { +public: + explicit IndexedName(const char *name = nullptr, int _index = 0) + : index(0) + { + if (!name) + this->type = ""; + else { + set(name); + if (_index) + this->index = _index; + } + } + + IndexedName(const char *name, + const std::vector & types, + bool allowOthers=true) + { + set(name, -1, types, allowOthers); + } + + explicit IndexedName(const QByteArray & data) + { + set(data.constData(), data.size()); + } + + IndexedName(const IndexedName &other) + : type(other.type), index(other.index) + {} + + static IndexedName fromConst(const char *name, int index) { + IndexedName res; + res.type = name; + res.index = index; + return res; + } + + IndexedName & operator=(const IndexedName & other) + { + this->index = other.index; + this->type = other.type; + return *this; + } + + friend std::ostream & operator<<(std::ostream & s, const IndexedName & e) + { + s << e.type; + if (e.index > 0) + s << e.index; + return s; + } + + bool operator==(const IndexedName & other) const + { + return this->index == other.index + && (this->type == other.type + || std::strcmp(this->type, other.type)==0); + } + + IndexedName & operator+=(int offset) + { + this->index += offset; + assert(this->index >= 0); + return *this; + } + + IndexedName & operator++() + { + ++this->index; + return *this; + } + + IndexedName & operator--() + { + --this->index; + assert(this->index >= 0); + return *this; + } + + bool operator!=(const IndexedName & other) const + { + return !(this->operator==(other)); + } + + const char * toString(std::string & s) const + { + // Note! s is not cleared on purpose. + std::size_t offset = s.size(); + s += this->type; + if (this->index > 0) + s += std::to_string(this->index); + return s.c_str() + offset; + } + + int compare(const IndexedName & other) const + { + int res = std::strcmp(this->type, other.type); + if (res) + return res; + if (this->index < other.index) + return -1; + if (this->index > other.index) + return 1; + return 0; + } + + bool operator<(const IndexedName & other) const + { + return compare(other) < 0; + } + + char operator[](int index) const + { + return this->type[index]; + } + + const char * getType() const { return this->type; } + + int getIndex() const { return this->index; } + + void setIndex(int index) { assert(index>=0); this->index = index; } + + bool isNull() const { return !this->type[0]; } + + explicit operator bool() const { return !isNull(); } + +protected: + void set(const char *, + int len = -1, + const std::vector &types = {}, + bool allowOthers = true); + +private: + const char * type; + int index; +}; + +} + +#endif _IndexedName_h_ From 38901f194b12383a349b9d4e4ba1428773209402 Mon Sep 17 00:00:00 2001 From: Adrian Insaurralde Avalos Date: Sat, 11 Mar 2023 23:00:29 -0300 Subject: [PATCH 02/12] fix #8847 sketcher dialog tool buttons bad styling --- src/Gui/Stylesheets/Behave-dark.qss | 27 ++++++++++++++++++++++++++ src/Gui/Stylesheets/Dark-blue.qss | 27 ++++++++++++++++++++++++++ src/Gui/Stylesheets/Dark-contrast.qss | 27 ++++++++++++++++++++++++++ src/Gui/Stylesheets/Dark-green.qss | 27 ++++++++++++++++++++++++++ src/Gui/Stylesheets/Dark-orange.qss | 27 ++++++++++++++++++++++++++ src/Gui/Stylesheets/Darker-blue.qss | 27 ++++++++++++++++++++++++++ src/Gui/Stylesheets/Darker-green.qss | 27 ++++++++++++++++++++++++++ src/Gui/Stylesheets/Darker-orange.qss | 27 ++++++++++++++++++++++++++ src/Gui/Stylesheets/Light-blue.qss | 27 ++++++++++++++++++++++++++ src/Gui/Stylesheets/Light-green.qss | 27 ++++++++++++++++++++++++++ src/Gui/Stylesheets/Light-orange.qss | 27 ++++++++++++++++++++++++++ src/Gui/Stylesheets/ProDark.qss | 28 ++++++++++++++++++++++++++- 12 files changed, 324 insertions(+), 1 deletion(-) diff --git a/src/Gui/Stylesheets/Behave-dark.qss b/src/Gui/Stylesheets/Behave-dark.qss index fdf97a21c1..46c79a2ef2 100644 --- a/src/Gui/Stylesheets/Behave-dark.qss +++ b/src/Gui/Stylesheets/Behave-dark.qss @@ -1648,6 +1648,33 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #65A2E5, stop:1 #65A2E5); } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: #D2D8E1; + background: #232932; + padding: 0px; + margin: 0px; +} + /*================================================================================================== Radio button diff --git a/src/Gui/Stylesheets/Dark-blue.qss b/src/Gui/Stylesheets/Dark-blue.qss index b004f7891f..6edcdb6cb9 100644 --- a/src/Gui/Stylesheets/Dark-blue.qss +++ b/src/Gui/Stylesheets/Dark-blue.qss @@ -1615,6 +1615,33 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #3874f2, stop:1 #5e90fa); } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: #e0e0e0; + background-color: #5a5a5a; + padding: 0px; + margin: 0px; +} + /*================================================================================================== Radio button diff --git a/src/Gui/Stylesheets/Dark-contrast.qss b/src/Gui/Stylesheets/Dark-contrast.qss index 3606d183ce..adc247d0dc 100644 --- a/src/Gui/Stylesheets/Dark-contrast.qss +++ b/src/Gui/Stylesheets/Dark-contrast.qss @@ -1615,6 +1615,33 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #1b3774, stop:1 #2053c0); } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: #fefefe; + background-color: #111111; + padding: 0px; + margin: 0px; +} + /*================================================================================================== QComboBox inside Task Panel content diff --git a/src/Gui/Stylesheets/Dark-green.qss b/src/Gui/Stylesheets/Dark-green.qss index 1f090bb4dd..b09e735dd9 100644 --- a/src/Gui/Stylesheets/Dark-green.qss +++ b/src/Gui/Stylesheets/Dark-green.qss @@ -1614,6 +1614,33 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #819c0c, stop:1 #94b30f); } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: #e0e0e0; + background-color: #5a5a5a; + padding: 0px; + margin: 0px; +} + /*================================================================================================== Radio button diff --git a/src/Gui/Stylesheets/Dark-orange.qss b/src/Gui/Stylesheets/Dark-orange.qss index 2b1cd513d3..24d8ab3902 100644 --- a/src/Gui/Stylesheets/Dark-orange.qss +++ b/src/Gui/Stylesheets/Dark-orange.qss @@ -1615,6 +1615,33 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #d0970c, stop:1 #daa116); } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: #e0e0e0; + background-color: #5a5a5a; + padding: 0px; + margin: 0px; +} + /*================================================================================================== Radio button diff --git a/src/Gui/Stylesheets/Darker-blue.qss b/src/Gui/Stylesheets/Darker-blue.qss index 2367454529..cf53333a9a 100644 --- a/src/Gui/Stylesheets/Darker-blue.qss +++ b/src/Gui/Stylesheets/Darker-blue.qss @@ -1615,6 +1615,33 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #1b3774, stop:1 #2053c0); } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: #f5f5f5; + background: #2a2a2a; + padding: 0px; + margin: 0px; +} + /*================================================================================================== QComboBox inside Task Panel content diff --git a/src/Gui/Stylesheets/Darker-green.qss b/src/Gui/Stylesheets/Darker-green.qss index a6e72ab654..708256a327 100644 --- a/src/Gui/Stylesheets/Darker-green.qss +++ b/src/Gui/Stylesheets/Darker-green.qss @@ -1615,6 +1615,33 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #566214, stop:1 #74831d); } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: #f5f5f5; + background: #2a2a2a; + padding: 0px; + margin: 0px; +} + /*================================================================================================== QComboBox inside Task Panel content diff --git a/src/Gui/Stylesheets/Darker-orange.qss b/src/Gui/Stylesheets/Darker-orange.qss index defd6a76aa..7fc5e9874e 100644 --- a/src/Gui/Stylesheets/Darker-orange.qss +++ b/src/Gui/Stylesheets/Darker-orange.qss @@ -1609,6 +1609,33 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #624b14, stop:1 #b28416); } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: #f5f5f5; + background-color: #2a2a2a; + padding: 0px; + margin: 0px; +} + /*================================================================================================== QComboBox inside Task Panel content diff --git a/src/Gui/Stylesheets/Light-blue.qss b/src/Gui/Stylesheets/Light-blue.qss index 2136fdcee3..cad26b097a 100644 --- a/src/Gui/Stylesheets/Light-blue.qss +++ b/src/Gui/Stylesheets/Light-blue.qss @@ -1612,6 +1612,33 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #3874f2, stop:1 #5e90fa); } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: black; + background: #f5f5f5; + padding: 0px; + margin: 0px; +} + /*================================================================================================== Radio button diff --git a/src/Gui/Stylesheets/Light-green.qss b/src/Gui/Stylesheets/Light-green.qss index a384a48428..5c575cce93 100644 --- a/src/Gui/Stylesheets/Light-green.qss +++ b/src/Gui/Stylesheets/Light-green.qss @@ -1612,6 +1612,33 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #819c0c, stop:1 #94b30f); } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: black; + background: #f5f5f5; + padding: 0px; + margin: 0px; +} + /*================================================================================================== Radio button diff --git a/src/Gui/Stylesheets/Light-orange.qss b/src/Gui/Stylesheets/Light-orange.qss index 65b02d63dd..f6ff4c8bd1 100644 --- a/src/Gui/Stylesheets/Light-orange.qss +++ b/src/Gui/Stylesheets/Light-orange.qss @@ -1612,6 +1612,33 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #d0970c, stop:1 #daa116); } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: black; + background: #f5f5f5; + padding: 0px; + margin: 0px; +} + /*================================================================================================== Radio button diff --git a/src/Gui/Stylesheets/ProDark.qss b/src/Gui/Stylesheets/ProDark.qss index d1b65a7f14..c57d0e10c8 100644 --- a/src/Gui/Stylesheets/ProDark.qss +++ b/src/Gui/Stylesheets/ProDark.qss @@ -1815,6 +1815,32 @@ QSint--ActionGroup QFrame[class="content"] QToolButton:pressed { background-color: #557BB6; } +/* QToolButtons with a menu found in Sketcher task panel*/ +QSint--ActionGroup QToolButton::menu-button { + border: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + padding: 2px; + width: 16px; /* 16px width + 4px for border = 20px allocated above */ + outline: none; + background-color: transparent; +} + +QSint--ActionGroup QToolButton#settingsButton, +QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#manualUpdate { + padding: 2px; + padding-right: 20px; /* make way for the popup button */ + margin: 0px; +} + +/* to give widget inside the menu same look as regular menu */ +QSint--ActionGroup QToolButton#filterButton QListWidget { + color: #f5f5f5; + background: #2a2a2a; + padding: 0px; + margin: 0px; +} /*================================================================================================== QComboBox inside Task Panel content @@ -1824,7 +1850,7 @@ QComboBox inside Task Panel content /* TODO: external border not working, in the rest of GUI works setting up Qmenu background color but inside Task Panel it doesn't... */ QSint--ActionGroup QFrame[class="content"] QMenu, QSint--ActionGroup QFrame[class="content"] QMenu::item { - background-color: #696969; + background-color: #2a2a2a; } QSint--ActionGroup QFrame[class="content"] QComboBox QAbstractItemView { From 42a91608c4849f5af4130604d118d1ad07c0a25c Mon Sep 17 00:00:00 2001 From: luzpaz Date: Tue, 14 Mar 2023 11:23:39 +0000 Subject: [PATCH 03/12] Refine previous fix for #8866 --- src/Base/Factory.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Base/Factory.h b/src/Base/Factory.h index 116cc21391..76d4e7a792 100644 --- a/src/Base/Factory.h +++ b/src/Base/Factory.h @@ -49,8 +49,8 @@ public: /** Base class of all factories - * This class has the purpose to produce a runtime instance - * of classes unknown at compile time. It holds a map of so called + * This class has the purpose to produce instances of classes at runtime + * that are unknown at compile time. It holds a map of so called * producers which are able to produce an instance of a special class. * Producer can be registered at runtime through e.g. application modules */ From c444fe823b5c9eeb1740a55ad34620645894946e Mon Sep 17 00:00:00 2001 From: berniev Date: Tue, 14 Mar 2023 14:14:04 +1000 Subject: [PATCH 04/12] Remove original proof of concept test files as some fail, and that won't work too well with future possible CI --- tests/src/Misc/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/src/Misc/CMakeLists.txt b/tests/src/Misc/CMakeLists.txt index 1ee958dea6..620e993b4e 100644 --- a/tests/src/Misc/CMakeLists.txt +++ b/tests/src/Misc/CMakeLists.txt @@ -1,7 +1,5 @@ target_sources( Tests_run PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/test1.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test2.cpp ${CMAKE_CURRENT_SOURCE_DIR}/fmt.cpp ) From 6e9817025ed0163028c8a2b36d2d5c96979693cc Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 13 Mar 2023 16:51:24 +0100 Subject: [PATCH 05/12] Clean mod/import folder Clean mod/import folder fix code not removed. --- src/Mod/Import/App/AppImport.cpp | 2 - src/Mod/Import/App/AppImportPy.cpp | 125 +--------------------- src/Mod/Import/App/FeatureImportIges.cpp | 33 +----- src/Mod/Import/App/FeatureImportIges.h | 13 +-- src/Mod/Import/App/FeatureImportStep.cpp | 30 +----- src/Mod/Import/App/FeatureImportStep.h | 9 +- src/Mod/Import/App/ImportOCAF2.cpp | 9 -- src/Mod/Import/App/ImportOCAFAssembly.cpp | 24 ----- src/Mod/Import/App/StepShape.cpp | 14 +-- src/Mod/Import/Gui/AppImportGuiPy.cpp | 29 +---- 10 files changed, 11 insertions(+), 277 deletions(-) diff --git a/src/Mod/Import/App/AppImport.cpp b/src/Mod/Import/App/AppImport.cpp index 1ddba87ec9..db9c05f264 100644 --- a/src/Mod/Import/App/AppImport.cpp +++ b/src/Mod/Import/App/AppImport.cpp @@ -49,8 +49,6 @@ PyMOD_INIT_FUNC(Import) // add mesh elements Base::Interpreter().addType(&Import::StepShapePy ::Type, importModule, "StepShape"); - // init Type system - //Import::StepShape ::init(); Base::Console().Log("Loading Import module... done\n"); PyMOD_Return(importModule); diff --git a/src/Mod/Import/App/AppImportPy.cpp b/src/Mod/Import/App/AppImportPy.cpp index 0b0a4ac963..9b7b5118c8 100644 --- a/src/Mod/Import/App/AppImportPy.cpp +++ b/src/Mod/Import/App/AppImportPy.cpp @@ -104,9 +104,6 @@ public: add_keyword_method("insert",&Module::importer, "insert(string,string) -- Insert the file into the given document." ); -// add_varargs_method("openAssembly",&Module::importAssembly, -// "openAssembly(string) -- Open the assembly file and create a new document." -// ); add_keyword_method("export",&Module::exporter, "export(list,string) -- Export a list of objects into a single file." ); @@ -144,7 +141,6 @@ private: std::string name8bit = Part::encodeFilename(Utf8Name); try { - //Base::Console().Log("Insert in Part with %s",Name); Base::FileInfo file(Utf8Name.c_str()); App::Document *pcDoc = nullptr; @@ -231,7 +227,6 @@ private: throw Py::Exception(PyExc_IOError, "no supported file format"); } -#if 1 ImportOCAFExt ocaf(hDoc, pcDoc, file.fileNamePure()); ocaf.setImportOptions(ImportOCAFExt::customImportOptions()); if (merge != Py_None) @@ -243,14 +238,7 @@ private: if (mode >= 0) ocaf.setMode(mode); ocaf.loadShapes(); -#elif 1 - Import::ImportOCAFCmd ocaf(hDoc, pcDoc, file.fileNamePure()); - ocaf.loadShapes(); -#else - Import::ImportXCAF xcaf(hDoc, pcDoc, file.fileNamePure()); - xcaf.loadShapes(); - pcDoc->recompute(); -#endif + hApp->Close(hDoc); if (!ocaf.partColors.empty()) { @@ -326,7 +314,6 @@ private: ocaf.exportObjects(objs); } else { - //bool keepExplicitPlacement = objs.size() > 1; bool keepExplicitPlacement = Standard_True; ExportOCAF ocaf(hDoc, keepExplicitPlacement); // That stuff is exporting a list of selected objects into FreeCAD Tree @@ -340,26 +327,20 @@ private: std::vector FreeLabels; std::vector part_id; ocaf.getFreeLabels(hierarchical_label,FreeLabels, part_id); -#if OCC_VERSION_HEX >= 0x070200 // Update is not performed automatically anymore: https://tracker.dev.opencascade.org/view.php?id=28055 XCAFDoc_DocumentTool::ShapeTool(hDoc->Main())->UpdateAssemblies(); -#endif } Base::FileInfo file(Utf8Name.c_str()); if (file.hasExtension("stp") || file.hasExtension("step")) { - //Interface_Static::SetCVal("write.step.schema", "AP214IS"); STEPCAFControl_Writer writer; Part::Interface::writeStepAssembly(Part::Interface::Assembly::On); - // writer.SetColorMode(Standard_False); writer.Transfer(hDoc, STEPControl_AsIs); APIHeaderSection_MakeHeader makeHeader(writer.ChangeWriter().Model()); Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/Part")->GetGroup("STEP"); - // https://forum.freecadweb.org/viewtopic.php?f=8&t=52967 - //makeHeader.SetName(new TCollection_HAsciiString((Standard_CString)Utf8Name.c_str())); makeHeader.SetAuthorValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Author", "Author").c_str())); makeHeader.SetOrganizationValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Company").c_str())); makeHeader.SetOriginatingSystem(new TCollection_HAsciiString(App::Application::getExecutableName().c_str())); @@ -690,111 +671,7 @@ private: throw Py::TypeError("expected ([DocObject],path"); } }; -/* -static PyObject * importAssembly(PyObject *self, PyObject *args) -{ - char* Name; - PyObject* TargetObjectPy=0; - if (!PyArg_ParseTuple(args, "et|O!","utf-8",&Name,&(App::DocumentObjectPy::Type),&TargetObjectPy)) - return 0; - std::string Utf8Name = std::string(Name); - PyMem_Free(Name); - std::string name8bit = Part::encodeFilename(Utf8Name); - PY_TRY { - //Base::Console().Log("Insert in Part with %s",Name); - Base::FileInfo file(name8bit); - - App::DocumentObject* target = nullptr; - - if(TargetObjectPy) - target = static_cast(TargetObjectPy)->getDocumentObjectPtr(); - - - App::Document *pcDoc = 0; - - pcDoc = App::GetApplication().getActiveDocument(); - - if (!pcDoc) - pcDoc = App::GetApplication().newDocument("ImportedAssembly"); - - - Handle(XCAFApp_Application) hApp = XCAFApp_Application::GetApplication(); - Handle(TDocStd_Document) hDoc; - hApp->NewDocument(TCollection_ExtendedString("MDTV-CAF"), hDoc); - - if (file.hasExtension("stp") || file.hasExtension("step")) { - try { - STEPCAFControl_Reader aReader; - aReader.SetColorMode(true); - aReader.SetNameMode(true); - aReader.SetLayerMode(true); - if (aReader.ReadFile((Standard_CString)(name8bit.c_str())) != IFSelect_RetDone) { - PyErr_SetString(PyExc_IOError, "cannot read STEP file"); - return 0; - } - - Handle(Message_ProgressIndicator) pi = new Part::ProgressIndicator(100); - aReader.Reader().WS()->MapReader()->SetProgress(pi); - pi->NewScope(100, "Reading STEP file..."); - pi->Show(); - aReader.Transfer(hDoc); - pi->EndScope(); - } - catch (OSD_Exception& e) { - Base::Console().Error("%s\n", e.GetMessageString()); - Base::Console().Message("Try to load STEP file without colors...\n"); - - Part::ImportStepParts(pcDoc,Name); - pcDoc->recompute(); - } - } - else if (file.hasExtension("igs") || file.hasExtension("iges")) { - try { - IGESControl_Controller::Init(); - Interface_Static::SetIVal("read.surfacecurve.mode",3); - IGESCAFControl_Reader aReader; - aReader.SetColorMode(true); - aReader.SetNameMode(true); - aReader.SetLayerMode(true); - if (aReader.ReadFile((Standard_CString)(name8bit.c_str())) != IFSelect_RetDone) { - PyErr_SetString(PyExc_IOError, "cannot read IGES file"); - return 0; - } - - Handle(Message_ProgressIndicator) pi = new Part::ProgressIndicator(100); - aReader.WS()->MapReader()->SetProgress(pi); - pi->NewScope(100, "Reading IGES file..."); - pi->Show(); - aReader.Transfer(hDoc); - pi->EndScope(); - } - catch (OSD_Exception& e) { - Base::Console().Error("%s\n", e.GetMessageString()); - Base::Console().Message("Try to load IGES file without colors...\n"); - - Part::ImportIgesParts(pcDoc,Name); - pcDoc->recompute(); - } - } - else { - PyErr_SetString(PyExc_RuntimeError, "no supported file format"); - return 0; - } - - Import::ImportOCAFAssembly ocaf(hDoc, pcDoc, file.fileNamePure(),target); - ocaf.loadAssembly(); - pcDoc->recompute(); - - } - catch (Standard_Failure& e) { - PyErr_SetString(PyExc_RuntimeError, e.GetMessageString()); - return 0; - } - PY_CATCH - - Py_Return; -}*/ PyObject* initModule() { diff --git a/src/Mod/Import/App/FeatureImportIges.cpp b/src/Mod/Import/App/FeatureImportIges.cpp index 7a644dd05b..f51f60a825 100644 --- a/src/Mod/Import/App/FeatureImportIges.cpp +++ b/src/Mod/Import/App/FeatureImportIges.cpp @@ -41,24 +41,11 @@ void FeatureImportIges::InitLabel(const TDF_Label &rcLabel) addProperty("String","FileName"); } -/* -bool FeaturePartImportStep::MustExecute(void) -{ - Base::Console().Log("PartBoxFeature::MustExecute()\n"); - return false; -} -*/ + Standard_Integer FeatureImportIges::Execute(void) { Base::Console().Log("FeaturePartImportIges::Execute()\n"); -/* cout << GetFloatProperty("x") << endl; - cout << GetFloatProperty("y") << endl; - cout << GetFloatProperty("z") << endl; - cout << GetFloatProperty("l") << endl; - cout << GetFloatProperty("h") << endl; - cout << GetFloatProperty("w") << endl;*/ - try{ IGESControl_Reader aReader; @@ -83,10 +70,6 @@ Standard_Integer FeatureImportIges::Execute(void) if (aReader.ReadFile((const Standard_CString)FileName.c_str()) != IFSelect_RetDone) throw Base::FileException("IGES read failed (load file)"); - // check iges-file (memory) - //if (!aReader.Check(Standard_True)) - // Base::Console().Warning( "IGES model contains errors! try loading anyway....\n" ); - // make brep aReader.TransferRoots(); // one shape, who contain's all subshapes @@ -104,17 +87,3 @@ Standard_Integer FeatureImportIges::Execute(void) return 0; } -/* -void FeatureImportIges::Validate(void) -{ - Base::Console().Log("FeaturePartImportStep::Validate()\n"); - - // We validate the object label ( Label() ), all the arguments and the results of the object: - log.SetValid(Label(), Standard_True); - - -} -*/ - - - diff --git a/src/Mod/Import/App/FeatureImportIges.h b/src/Mod/Import/App/FeatureImportIges.h index 13a9597b1a..d302e253cf 100644 --- a/src/Mod/Import/App/FeatureImportIges.h +++ b/src/Mod/Import/App/FeatureImportIges.h @@ -36,21 +36,12 @@ public: virtual void InitLabel(const TDF_Label &rcLabel); -// virtual bool MustExecute(void); - virtual Standard_Integer Execute(void); -// virtual void Validate(void); - - /// Returns the Name/Type of the feature - virtual const char *Type(void){return "PartImportIges";} + /// Returns the Name/Type of the feature + virtual const char *Type(void){return "PartImportIges";} }; - - } - - - #endif // __FeaturePartImportIges_H__ diff --git a/src/Mod/Import/App/FeatureImportStep.cpp b/src/Mod/Import/App/FeatureImportStep.cpp index 6a71e94039..b82d7a13ff 100644 --- a/src/Mod/Import/App/FeatureImportStep.cpp +++ b/src/Mod/Import/App/FeatureImportStep.cpp @@ -42,24 +42,11 @@ void FeatureImportStep::InitLabel(const TDF_Label &rcLabel) } -/* -bool FeaturePartImportStep::MustExecute(void) -{ - Base::Console().Log("PartBoxFeature::MustExecute()\n"); - return false; -} -*/ + Standard_Integer FeatureImportStep::Execute(void) { Base::Console().Log("FeaturePartImportStep::Execute()\n"); -/* cout << GetFloatProperty("x") << endl; - cout << GetFloatProperty("y") << endl; - cout << GetFloatProperty("z") << endl; - cout << GetFloatProperty("l") << endl; - cout << GetFloatProperty("h") << endl; - cout << GetFloatProperty("w") << endl;*/ - try{ STEPControl_Reader aReader; @@ -92,7 +79,7 @@ Standard_Integer FeatureImportStep::Execute(void) // Root transfers Standard_Integer nbr = aReader.NbRootsForTransfer(); - //aReader.PrintCheckTransfer (failsonly, IFSelect_ItemsByEntity); + for ( Standard_Integer n = 1; n<= nbr; n++) { printf("STEP: Transferring Root %d\n",n); @@ -124,16 +111,3 @@ Standard_Integer FeatureImportStep::Execute(void) return 0; } -/* -void FeatureImportStep::Validate(void) -{ - Base::Console().Log("FeaturePartImportStep::Validate()\n"); - - // We validate the object label ( Label() ), all the arguments and the results of the object: - log.SetValid(Label(), Standard_True); - - -} -*/ - - diff --git a/src/Mod/Import/App/FeatureImportStep.h b/src/Mod/Import/App/FeatureImportStep.h index dbb297d9b7..c6af747d27 100644 --- a/src/Mod/Import/App/FeatureImportStep.h +++ b/src/Mod/Import/App/FeatureImportStep.h @@ -37,17 +37,12 @@ public: virtual Standard_Integer Execute(void); -// virtual void Validate(void); - - /// Returns the Name/Type of the feature - virtual const char *Type(void){return "PartImportStep";} + /// Returns the Name/Type of the feature + virtual const char *Type(void){return "PartImportStep";} }; - } - - #endif // __FeatureImportStep_H__ diff --git a/src/Mod/Import/App/ImportOCAF2.cpp b/src/Mod/Import/App/ImportOCAF2.cpp index a3a4c251a1..fde27edcd2 100644 --- a/src/Mod/Import/App/ImportOCAF2.cpp +++ b/src/Mod/Import/App/ImportOCAF2.cpp @@ -560,9 +560,7 @@ bool ImportOCAF2::createGroup(App::Document *doc, Info &info, const TopoDS_Shape link->Placement.setValue(pla->getValue()); child = link; } - // child->Visibility.setValue(false); } - // group->Visibility.setValue(false); group->ElementList.setValues(children); group->VisibilityList.setValue(visibilities); info.obj = group; @@ -627,7 +625,6 @@ App::DocumentObject* ImportOCAF2::loadShapes() ret = info.obj; } if(ret) { - // ret->Visibility.setValue(true); ret->recomputeFeature(true); } if(options.merge && ret && !ret->isDerivedFrom(Part::Feature::getClassTypeId())) { @@ -766,7 +763,6 @@ App::DocumentObject *ImportOCAF2::loadShape(App::Document *doc, } auto link = static_cast(doc->addObject("App::Link","Link")); - // link->Visibility.setValue(false); link->setLink(-1,info.obj); setPlacement(&link->Placement,shape); info.obj = link; @@ -861,7 +857,6 @@ bool ImportOCAF2::createAssembly(App::Document *_doc, // Okay, we are creating a link array auto link = static_cast(doc->addObject("App::Link","Link")); - // link->Visibility.setValue(false); link->setLink(-1,child); link->ShowElement.setValue(false); link->ElementCount.setValue(childInfo.plas.size()); @@ -1182,11 +1177,7 @@ void ExportOCAF2::exportObjects(std::vector &objs, const c if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) dumpLabels(pDoc->Main(),aShapeTool,aColorTool); - -#if OCC_VERSION_HEX >= 0x070200 - // Update is not performed automatically anymore: https://tracker.dev.opencascade.org/view.php?id=28055 aShapeTool->UpdateAssemblies(); -#endif } TDF_Label ExportOCAF2::exportObject(App::DocumentObject* parentObj, diff --git a/src/Mod/Import/App/ImportOCAFAssembly.cpp b/src/Mod/Import/App/ImportOCAFAssembly.cpp index 720f2f5a48..a95c981993 100644 --- a/src/Mod/Import/App/ImportOCAFAssembly.cpp +++ b/src/Mod/Import/App/ImportOCAFAssembly.cpp @@ -91,21 +91,6 @@ std::string ImportOCAFAssembly::getName(const TDF_Label& label) part_name = str; delete [] str; return part_name; - - //if (part_name.empty()) { - // return ""; - //} - //else { - // bool ws=true; - // for (std::string::iterator it = part_name.begin(); it != part_name.end(); ++it) { - // if (*it != ' ') { - // ws = false; - // break; - // } - // } - // if (ws) - // part_name = defaultname; - //} } return ""; @@ -255,15 +240,6 @@ void ImportOCAFAssembly::createShape(const TopoDS_Shape& aShape, const TopLoc_Lo std::vector colors; colors.push_back(color); applyColors(part, colors); -#if 0//TODO - Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(part); - if (vp && vp->isDerivedFrom(PartGui::ViewProviderPart::getClassTypeId())) { - color.r = aColor.Red(); - color.g = aColor.Green(); - color.b = aColor.Blue(); - static_cast(vp)->ShapeColor.setValue(color); - } -#endif } TopTools_IndexedMapOfShape faces; diff --git a/src/Mod/Import/App/StepShape.cpp b/src/Mod/Import/App/StepShape.cpp index 91d6815a09..52260a40ef 100644 --- a/src/Mod/Import/App/StepShape.cpp +++ b/src/Mod/Import/App/StepShape.cpp @@ -65,25 +65,13 @@ int StepShape::read(const char* fileName) throw Base::FileException("Cannot open STEP file"); } - //Standard_Integer ic = Interface_Static::IVal("read.precision.mode"); - //Standard_Real rp = Interface_Static::RVal("read.maxprecision.val"); - //Standard_Integer ic = Interface_Static::IVal("read.maxprecision.mode"); - //Standard_Integer mv = Interface_Static::IVal("read.stdsameparameter.mode"); - //Standard_Integer rp = Interface_Static::IVal("read.surfacecurve.mode"); - //Standard_Real era = Interface_Static::RVal("read.encoderegularity.angle"); - //Standard_Integer ic = Interface_Static::IVal("read.step.product.mode"); - //Standard_Integer ic = Interface_Static::IVal("read.step.product.context"); - //Standard_Integer ic = Interface_Static::IVal("read.step.shape.repr"); - //Standard_Integer ic = Interface_Static::IVal("read.step.assembly.level"); - //Standard_Integer ic = Interface_Static::IVal("read.step.shape.relationship"); - //Standard_Integer ic = Interface_Static::IVal("read.step.shape.aspect"); Handle(TColStd_HSequenceOfTransient) list = aReader.GiveList(); //Use method StepData_StepModel::NextNumberForLabel to find its rank with the following: //Standard_CString label = "#..."; Handle(StepData_StepModel) model = aReader.StepModel(); - //rank = model->NextNumberForLabe(label, 0, Standard_False); + std::cout << "dump of step header:" << std::endl; #if OCC_VERSION_HEX < 0x070401 diff --git a/src/Mod/Import/Gui/AppImportGuiPy.cpp b/src/Mod/Import/Gui/AppImportGuiPy.cpp index acdc260833..4d0e1c2f4f 100644 --- a/src/Mod/Import/Gui/AppImportGuiPy.cpp +++ b/src/Mod/Import/Gui/AppImportGuiPy.cpp @@ -180,15 +180,12 @@ void OCAFBrowser::load(const TDF_Label& label, QTreeWidgetItem* item, const QStr item->setText(0, text); } -#if 0 - TDF_IDList localList = myList; -#else + TDF_IDList localList; TDF_AttributeIterator itr (label); for ( ; itr.More(); itr.Next()) { localList.Append(itr.Value()->ID()); } -#endif for (TDF_ListIteratorOfIDList it(localList); it.More(); it.Next()) { Handle(TDF_Attribute) attr; @@ -260,15 +257,6 @@ void OCAFBrowser::load(const TDF_Label& label, QTreeWidgetItem* item, const QStr } } - //TDF_ChildIDIterator nodeIterator(label, XCAFDoc::ShapeRefGUID()); - //for (; nodeIterator.More(); nodeIterator.Next()) { - // Handle(TDataStd_TreeNode) node = Handle(TDataStd_TreeNode)::DownCast(nodeIterator.Value()); - // //if (node->HasFather()) - // // ; - // QTreeWidgetItem* child = new QTreeWidgetItem(); - // child->setText(0, QString::fromLatin1("TDataStd_TreeNode")); - // item->addChild(child); - //} int i=1; for (TDF_ChildIterator it(label); it.More(); it.Next(),i++) { @@ -295,12 +283,9 @@ private: if (!vp) return; if(colors.empty()) { - // vp->MapFaceColor.setValue(true); - // vp->MapLineColor.setValue(true); - // vp->updateColors(0,true); return; } - // vp->MapFaceColor.setValue(false); + if(colors.size() == 1) { vp->ShapeColor.setValue(colors.front()); vp->Transparency.setValue(100 * colors.front().a); @@ -313,7 +298,6 @@ private: auto vp = dynamic_cast(Gui::Application::Instance->getViewProvider(part)); if (!vp) return; - // vp->MapLineColor.setValue(false); if(colors.size() == 1) vp->LineColor.setValue(colors.front()); else @@ -344,7 +328,6 @@ private: if(!vp) return; (void)colors; - // vp->setElementColors(colors); } }; @@ -411,7 +394,6 @@ private: std::string name8bit = Part::encodeFilename(Utf8Name); try { - //Base::Console().Log("Insert in Part with %s",Name); Base::FileInfo file(Utf8Name.c_str()); App::Document *pcDoc = nullptr; @@ -666,10 +648,7 @@ private: ocaf.getPartColors(hierarchical_part,FreeLabels,part_id,Colors); ocaf.reallocateFreeShape(hierarchical_part,FreeLabels,part_id,Colors); -#if OCC_VERSION_HEX >= 0x070200 - // Update is not performed automatically anymore: https://tracker.dev.opencascade.org/view.php?id=28055 XCAFDoc_DocumentTool::ShapeTool(hDoc->Main())->UpdateAssemblies(); -#endif } Base::FileInfo file(Utf8Name.c_str()); @@ -682,7 +661,6 @@ private: STEPCAFControl_Writer writer; Part::Interface::writeStepAssembly(Part::Interface::Assembly::On); - // writer.SetColorMode(Standard_False); writer.Transfer(hDoc, STEPControl_AsIs); // edit STEP header @@ -691,8 +669,6 @@ private: Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/Part")->GetGroup("STEP"); - // https://forum.freecadweb.org/viewtopic.php?f=8&t=52967 - //makeHeader.SetName(new TCollection_HAsciiString((Standard_CString)Utf8Name.c_str())); makeHeader.SetAuthorValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Author", "Author").c_str())); makeHeader.SetOrganizationValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Company").c_str())); makeHeader.SetOriginatingSystem(new TCollection_HAsciiString(App::Application::getExecutableName().c_str())); @@ -758,7 +734,6 @@ private: throw Py::Exception(); try { - //Base::Console().Log("Insert in Part with %s",Name); Base::FileInfo file(Name); Handle(XCAFApp_Application) hApp = XCAFApp_Application::GetApplication(); From 00ffa67cbd07ba74857af326f5936546144b87fb Mon Sep 17 00:00:00 2001 From: Paddle Date: Tue, 14 Mar 2023 14:38:25 +0100 Subject: [PATCH 06/12] Sketcher: Solver Message simplification --- src/Gui/Stylesheets/ProDark.qss | 14 --- src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp | 51 ++++---- src/Mod/Sketcher/Gui/TaskSketcherMessages.h | 5 +- src/Mod/Sketcher/Gui/TaskSketcherMessages.ui | 115 +++++------------- 4 files changed, 59 insertions(+), 126 deletions(-) diff --git a/src/Gui/Stylesheets/ProDark.qss b/src/Gui/Stylesheets/ProDark.qss index c57d0e10c8..abb5d330b1 100644 --- a/src/Gui/Stylesheets/ProDark.qss +++ b/src/Gui/Stylesheets/ProDark.qss @@ -1624,20 +1624,6 @@ QWidget#Form QPushButton:pressed { background-color: #557BB6; } -/* Sketcher Manual Update Button */ - -QPushButton#manualUpdate { - padding: 4px; - margin: 0px; - border: 1px solid #494949; -} - -QPushButton:pressed#manualUpdate { - color: #ffffff; - border: 1px solid #3c3c3c; - background-color: #48699a; -} - /* Addon Manager */ QDialog#Dialog QPushButton { diff --git a/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp b/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp index aa15f3accd..01d7c3a6b2 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherMessages.cpp @@ -21,6 +21,9 @@ ***************************************************************************/ #include "PreCompiled.h" +#ifndef _PreComp_ +# include +#endif #include #include @@ -52,14 +55,6 @@ TaskSketcherMessages::TaskSketcherMessages(ViewProviderSketch *sketchView) : ui->labelConstrainStatus->setOpenExternalLinks(false); - ui->autoUpdate->onRestore(); - ui->autoRemoveRedundants->onRestore(); - - if(ui->autoUpdate->isChecked()) - sketchView->getSketchObject()->noRecomputes=false; - else - sketchView->getSketchObject()->noRecomputes=true; - // Set up the possible state values for the status label ui->labelConstrainStatus->setParameterGroup("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); ui->labelConstrainStatus->registerState(QString::fromUtf8("empty_sketch"), QColor("black"), std::string("EmptySketchMessageColor")); @@ -77,14 +72,27 @@ TaskSketcherMessages::TaskSketcherMessages(ViewProviderSketch *sketchView) : connect(ui->labelConstrainStatusLink, &Gui::UrlLabel::linkClicked, this, &TaskSketcherMessages::on_labelConstrainStatusLink_linkClicked); + //Set Auto Update in the 'Manual Update' button menu. + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + bool state = hGrp->GetBool("AutoRecompute", false); + + sketchView->getSketchObject()->noRecomputes = !state; + + QAction* action = new QAction(tr("Auto update"), this); + action->setToolTip(tr("Executes a recomputation of active document after every sketch action")); + action->setCheckable(true); + action->setChecked(state); + ui->manualUpdate->addAction(action); + + QObject::connect( + qAsConst(ui->manualUpdate)->actions()[0], &QAction::changed, + this, &TaskSketcherMessages::onAutoUpdateStateChanged + ); + /*QObject::connect( ui->labelConstrainStatus, SIGNAL(linkActivated(const QString &)), this , SLOT (on_labelConstrainStatus_linkActivated(const QString &)) ); - QObject::connect( - ui->autoUpdate, SIGNAL(stateChanged(int)), - this , SLOT (on_autoUpdate_stateChanged(int)) - ); QObject::connect( ui->manualUpdate, SIGNAL(clicked(bool)), this , SLOT (on_manualUpdate_clicked(bool)) @@ -123,22 +131,13 @@ void TaskSketcherMessages::on_labelConstrainStatusLink_linkClicked(const QString } -void TaskSketcherMessages::on_autoUpdate_stateChanged(int state) +void TaskSketcherMessages::onAutoUpdateStateChanged() { - if(state==Qt::Checked) { - sketchView->getSketchObject()->noRecomputes=false; - ui->autoUpdate->onSave(); - } - else if (state==Qt::Unchecked) { - sketchView->getSketchObject()->noRecomputes=true; - ui->autoUpdate->onSave(); - } -} + bool state = qAsConst(ui->manualUpdate)->actions()[0]->isChecked(); -void TaskSketcherMessages::on_autoRemoveRedundants_stateChanged(int state) -{ - Q_UNUSED(state); - ui->autoRemoveRedundants->onSave(); + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + hGrp->SetBool("AutoRecompute", state); + sketchView->getSketchObject()->noRecomputes = !state; } void TaskSketcherMessages::on_manualUpdate_clicked(bool checked) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherMessages.h b/src/Mod/Sketcher/Gui/TaskSketcherMessages.h index 7d3a462a2d..feebe23e27 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherMessages.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherMessages.h @@ -47,12 +47,11 @@ public: explicit TaskSketcherMessages(ViewProviderSketch *sketchView); ~TaskSketcherMessages() override; - void slotSetUp(const QString &state, const QString &msg, const QString& link, const QString& linkText); + void slotSetUp(const QString& state, const QString& msg, const QString& link, const QString& linkText); private Q_SLOTS: void on_labelConstrainStatusLink_linkClicked(const QString &); - void on_autoUpdate_stateChanged(int state); - void on_autoRemoveRedundants_stateChanged(int state); + void onAutoUpdateStateChanged(); void on_manualUpdate_clicked(bool checked); protected: diff --git a/src/Mod/Sketcher/Gui/TaskSketcherMessages.ui b/src/Mod/Sketcher/Gui/TaskSketcherMessages.ui index 8c95744c99..38c8c0f41a 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherMessages.ui +++ b/src/Mod/Sketcher/Gui/TaskSketcherMessages.ui @@ -7,104 +7,53 @@ 0 0 253 - 108 + 48 Form - + - - - - - DOF - - - - - - - Link - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - New constraints that would be redundant will automatically be removed - + - Auto remove redundants - - - false - - - AutoRemoveRedundants - - - Mod/Sketcher + DOF - - - - - Executes a recomputation of active document after every sketch action - - - Auto update - - - false - - - AutoRecompute - - - Mod/Sketcher - - - - - - - Forces recomputation of active document - - - Update - - - - + + + Link + + + + + + + + 0 + 0 + + + + Forces recomputation of active document + + + + + + + :/icons/view-refresh.svg:/icons/view-refresh.svg + + + QToolButton::MenuButtonPopup + + - - Gui::PrefCheckBox - QCheckBox -
Gui/PrefWidgets.h
-
Gui::StatefulLabel QLabel From 71a94da497637cd3c6a891892d13d413805733db Mon Sep 17 00:00:00 2001 From: Paddle Date: Tue, 14 Mar 2023 15:11:25 +0100 Subject: [PATCH 07/12] Sketcher: Stylesheet, fix manualUpdate button style. --- src/Gui/Stylesheets/Behave-dark.qss | 2 +- src/Gui/Stylesheets/Dark-blue.qss | 2 +- src/Gui/Stylesheets/Dark-contrast.qss | 2 +- src/Gui/Stylesheets/Dark-green.qss | 2 +- src/Gui/Stylesheets/Dark-orange.qss | 2 +- src/Gui/Stylesheets/Darker-blue.qss | 2 +- src/Gui/Stylesheets/Darker-green.qss | 2 +- src/Gui/Stylesheets/Darker-orange.qss | 2 +- src/Gui/Stylesheets/Light-blue.qss | 2 +- src/Gui/Stylesheets/Light-green.qss | 2 +- src/Gui/Stylesheets/Light-orange.qss | 2 +- src/Gui/Stylesheets/ProDark.qss | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Gui/Stylesheets/Behave-dark.qss b/src/Gui/Stylesheets/Behave-dark.qss index 46c79a2ef2..13ef21cb80 100644 --- a/src/Gui/Stylesheets/Behave-dark.qss +++ b/src/Gui/Stylesheets/Behave-dark.qss @@ -1660,7 +1660,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ diff --git a/src/Gui/Stylesheets/Dark-blue.qss b/src/Gui/Stylesheets/Dark-blue.qss index 6edcdb6cb9..12f8edb93f 100644 --- a/src/Gui/Stylesheets/Dark-blue.qss +++ b/src/Gui/Stylesheets/Dark-blue.qss @@ -1627,7 +1627,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ diff --git a/src/Gui/Stylesheets/Dark-contrast.qss b/src/Gui/Stylesheets/Dark-contrast.qss index adc247d0dc..acf351997c 100644 --- a/src/Gui/Stylesheets/Dark-contrast.qss +++ b/src/Gui/Stylesheets/Dark-contrast.qss @@ -1627,7 +1627,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ diff --git a/src/Gui/Stylesheets/Dark-green.qss b/src/Gui/Stylesheets/Dark-green.qss index b09e735dd9..ed6ab3923b 100644 --- a/src/Gui/Stylesheets/Dark-green.qss +++ b/src/Gui/Stylesheets/Dark-green.qss @@ -1626,7 +1626,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ diff --git a/src/Gui/Stylesheets/Dark-orange.qss b/src/Gui/Stylesheets/Dark-orange.qss index 24d8ab3902..4e1432f149 100644 --- a/src/Gui/Stylesheets/Dark-orange.qss +++ b/src/Gui/Stylesheets/Dark-orange.qss @@ -1627,7 +1627,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ diff --git a/src/Gui/Stylesheets/Darker-blue.qss b/src/Gui/Stylesheets/Darker-blue.qss index cf53333a9a..31fc761cb5 100644 --- a/src/Gui/Stylesheets/Darker-blue.qss +++ b/src/Gui/Stylesheets/Darker-blue.qss @@ -1627,7 +1627,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ diff --git a/src/Gui/Stylesheets/Darker-green.qss b/src/Gui/Stylesheets/Darker-green.qss index 708256a327..004d55c3e7 100644 --- a/src/Gui/Stylesheets/Darker-green.qss +++ b/src/Gui/Stylesheets/Darker-green.qss @@ -1627,7 +1627,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ diff --git a/src/Gui/Stylesheets/Darker-orange.qss b/src/Gui/Stylesheets/Darker-orange.qss index 7fc5e9874e..e19faeb6cc 100644 --- a/src/Gui/Stylesheets/Darker-orange.qss +++ b/src/Gui/Stylesheets/Darker-orange.qss @@ -1621,7 +1621,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ diff --git a/src/Gui/Stylesheets/Light-blue.qss b/src/Gui/Stylesheets/Light-blue.qss index cad26b097a..cbb3ee70e1 100644 --- a/src/Gui/Stylesheets/Light-blue.qss +++ b/src/Gui/Stylesheets/Light-blue.qss @@ -1624,7 +1624,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ diff --git a/src/Gui/Stylesheets/Light-green.qss b/src/Gui/Stylesheets/Light-green.qss index 5c575cce93..6567b974e0 100644 --- a/src/Gui/Stylesheets/Light-green.qss +++ b/src/Gui/Stylesheets/Light-green.qss @@ -1624,7 +1624,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ diff --git a/src/Gui/Stylesheets/Light-orange.qss b/src/Gui/Stylesheets/Light-orange.qss index f6ff4c8bd1..3d3eee57f2 100644 --- a/src/Gui/Stylesheets/Light-orange.qss +++ b/src/Gui/Stylesheets/Light-orange.qss @@ -1624,7 +1624,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ diff --git a/src/Gui/Stylesheets/ProDark.qss b/src/Gui/Stylesheets/ProDark.qss index abb5d330b1..d29ae2c954 100644 --- a/src/Gui/Stylesheets/ProDark.qss +++ b/src/Gui/Stylesheets/ProDark.qss @@ -1813,7 +1813,7 @@ QSint--ActionGroup QToolButton::menu-button { } QSint--ActionGroup QToolButton#settingsButton, -QSint--ActionGroup QToolButton#filterButton +QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { padding: 2px; padding-right: 20px; /* make way for the popup button */ From 45c0b634fa42c2a0326bb6c1c8b9d89076138713 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 7 Mar 2023 23:55:16 -0600 Subject: [PATCH 08/12] App: Clean up IndexedName and add tests Fixes the matching algorithm when provided a vector of existing names: The original algorithm was equivalent to a 'startswith' algorithm, when it should have been testing for exact and complete string equality. This also does some refactoring to rename and clarify variables and functions, simplifies some functions by using standard library calls when possible, and addresses various linter complaints. It also applies our current clang-format to the files. Co-authored-by: Ajinkya Dahale --- src/App/IndexedName.cpp | 129 ++++---- src/App/IndexedName.h | 255 ++++++++++++--- tests/src/App/CMakeLists.txt | 1 + tests/src/App/IndexedName.cpp | 595 ++++++++++++++++++++++++++++++++++ 4 files changed, 864 insertions(+), 116 deletions(-) create mode 100644 tests/src/App/IndexedName.cpp diff --git a/src/App/IndexedName.cpp b/src/App/IndexedName.cpp index e4bc448d4d..ebe9bf2ddb 100644 --- a/src/App/IndexedName.cpp +++ b/src/App/IndexedName.cpp @@ -2,6 +2,7 @@ /**************************************************************************** * Copyright (c) 2022 Zheng, Lei (realthunder) * + * Copyright (c) 2023 FreeCAD Project Association * * * * This file is part of FreeCAD. * * * @@ -21,6 +22,7 @@ * * ***************************************************************************/ +// NOLINTNEXTLINE #include "PreCompiled.h" #ifndef _PreComp_ @@ -28,100 +30,93 @@ # include #endif -#include - #include "IndexedName.h" using namespace Data; -struct ByteArray +/// Check whether the input character is an underscore or an ASCII letter a-Z or A-Z +inline bool isInvalidChar(char test) { - ByteArray(const QByteArray& b) - :bytes(b) - {} + return test != '_' && (test < 'a' || test > 'z' ) && (test < 'A' || test > 'Z'); +} - ByteArray(const ByteArray& other) - :bytes(other.bytes) - {} - - ByteArray(ByteArray&& other) - :bytes(std::move(other.bytes)) - {} - - void mutate() const - { - QByteArray copy; - copy.append(bytes.constData(), bytes.size()); - bytes = copy; - } - - bool operator==(const ByteArray& other) const { - return bytes == other.bytes; - } - - mutable QByteArray bytes; -}; - -struct ByteArrayHasher +/// Get the integer suffix of name. Returns a tuple of (suffix, suffixPosition). Calling code +/// should check to ensure that suffixPosition is not equal to nameLength (in which case there was no +/// suffix). +/// +/// \param name The name to check +/// \param nameLength The length of the string in name +/// \returns An integer pair of the suffix itself and the position of that suffix in name +std::pair getIntegerSuffix(const char *name, int nameLength) { - std::size_t operator()(const ByteArray& bytes) const - { - return qHash(bytes.bytes); - } + int suffixPosition {nameLength - 1}; - std::size_t operator()(const QByteArray& bytes) const - { - return qHash(bytes); + for (; suffixPosition >= 0; --suffixPosition) { + // When we support C++20 we can use std::span<> to eliminate the clang-tidy warning + // NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic + if (!isdigit(name[suffixPosition])) { + break; + } } -}; + ++suffixPosition; + int suffix {0}; + if (suffixPosition < nameLength) { + // When we support C++20 we can use std::span<> to eliminate the clang-tidy warning + // NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic + suffix = std::atoi(name + suffixPosition); + } + return std::make_pair(suffix, suffixPosition); +} void IndexedName::set( const char* name, - int len, - const std::vector& types, + int length, + const std::vector& allowedNames, bool allowOthers) { + // Storage for names that we weren't given external storage for static std::unordered_set NameSet; - if (len < 0) - len = static_cast(std::strlen(name)); - int i; - for (i = len - 1; i >= 0; --i) { - if (name[i] < '0' || name[i]>'9') - break; + if (length < 0) { + length = static_cast(std::strlen(name)); + } + // Name typically ends with an integer: find that integer + auto [suffix, suffixPosition] = getIntegerSuffix(name, length); + if (suffixPosition < length) { + this->index = suffix; } - ++i; - this->index = std::atoi(name + i); - for (int j = 0; j < i; ++j) { - if (name[j] == '_' - || (name[j] >= 'a' && name[j] <= 'z') - || (name[j] >= 'A' && name[j] <= 'Z')) - continue; + // Make sure that every character is either an ASCII letter (upper or lowercase), or an + // underscore. If any other character appears, reject the entire string. + // When we support C++20 we can use std::span<> to eliminate the clang-tidy warning + // NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic + if (std::any_of(name, name+suffixPosition, isInvalidChar)) { this->type = ""; return; } - for (const char* type : types) { - int j = 0; - for (const char* n = name, *t = type; *n; ++n) { - if (*n != *t || j >= i) - break; - ++j; - ++t; - if (!*t) { - this->type = type; - return; - } + // If a list of allowedNames was provided, see if our set name matches one of those allowedNames: if it + // does, reference that memory location and return. + for (const auto *typeName : allowedNames) { + if (std::strncmp(name, typeName, suffixPosition) == 0) { + this->type = typeName; + return; } } + // If the type was NOT in the list of allowedNames, but the caller has set the allowOthers flag to + // true, then add the new type to the static NameSet (if it is not already there). if (allowOthers) { - auto res = NameSet.insert(QByteArray::fromRawData(name, i)); - if (res.second) - res.first->mutate(); + auto res = NameSet.insert(ByteArray(QByteArray::fromRawData(name, suffixPosition))); + if (res.second /*The insert succeeded (the type was new)*/) { + // Make sure that the data in the set is a unique (unshared) copy of the text + res.first->ensureUnshared(); + } this->type = res.first->bytes.constData(); } - else + else { + // The passed-in type is not in the allowed list, and allowOthers was not true, so don't + // store the type this->type = ""; + } } diff --git a/src/App/IndexedName.h b/src/App/IndexedName.h index 3e64882934..85282b9545 100644 --- a/src/App/IndexedName.h +++ b/src/App/IndexedName.h @@ -2,6 +2,7 @@ /**************************************************************************** * Copyright (c) 2022 Zheng, Lei (realthunder) * + * Copyright (c) 2023 FreeCAD Project Association * * * * This file is part of FreeCAD. * * * @@ -21,9 +22,8 @@ * * ***************************************************************************/ - -#ifndef _IndexedName_h_ -#define _IndexedName_h_ +#ifndef APP_INDEXEDNAME_H +#define APP_INDEXEDNAME_H #include #include @@ -32,6 +32,7 @@ #include #include +#include #include "FCGlobal.h" @@ -39,65 +40,133 @@ namespace Data { +/// The IndexedName class provides a very memory-efficient data structure to hold a name and an index +/// value, and to perform various comparisons and validations of those values. The name must only +/// consist of upper- and lower-case ASCII characters and the underscore ('_') character. The index +/// must be a positive integer. The string representation of this IndexedName is the name followed by +/// the index, with no spaces between: an IndexedName may be constructed from this string. For +/// example "EDGE1" or "FACE345" might be the names of elements that use an IndexedName. If there is +/// then an "EDGE2", only a pointer to the original stored name "EDGE" is retained. +/// +/// The memory efficiency of the class comes from re-using the same character storage for names that +/// match, while retaining their differing indices. This is achieved by either using user-provided +/// const char * names (provided as a list of typeNames and presumed to never be deallocated), or by +/// maintaining an internal list of names that have been used before, and can be re-used later. class AppExport IndexedName { public: + + /// Construct from a name and an optional index. If the name contains an index it is read, but + /// is used as the index *only* if _index parameter is unset. If the _index parameter is given + /// it overrides any trailing integer in the name. Index must be positive, and name must contain + /// only ASCII letters and the underscore character. If these conditions are not met, name is + /// set to the empty string, and isNull() will return true. + /// + /// \param name The new name - ASCII letters and underscores only, with optional integer suffix. + /// This memory will be copied into a new internal storage location and need not be persistent. + /// \param _index The new index - if provided, it overrides any suffix provided by name explicit IndexedName(const char *name = nullptr, int _index = 0) : index(0) { - if (!name) + assert(_index >= 0); + if (!name) { this->type = ""; + } else { set(name); - if (_index) + if (_index > 0) { this->index = _index; + } } } + /// Create an indexed name that is restricted to a list of preset type names. If it appears in + /// that list, only a pointer to the character storage in the list is retained: the memory + /// locations pointed at by the list must never be destroyed once they have been used to create + /// names. If allowOthers is true (the default) then a requested name that is not in the list + /// will be added to a static internal storage table, and its memory then re-used for later + /// objects with the same name. If allowOthers is false, then the name request is rejected, and + /// the name is treated as null. + /// + /// \param name The new name - ASCII letters and underscores only, with optional integer suffix + /// \param allowedTypeNames A vector of allowed names. Storage locations must persist for the + /// entire run of the program. + /// \param allowOthers Whether a name not in allowedTypeNames is permitted. If true (the + /// default) then a name not in allowedTypeNames is added to a static internal storage vector + /// so that it can be re-used later without additional memory allocation. IndexedName(const char *name, - const std::vector & types, - bool allowOthers=true) + const std::vector & allowedTypeNames, + bool allowOthers=true) : type(""), index(0) { - set(name, -1, types, allowOthers); + set(name, -1, allowedTypeNames, allowOthers); } - explicit IndexedName(const QByteArray & data) + /// Construct from a QByteArray, but explicitly making a copy of the name on its first + /// occurrence. If this is a name that has already been stored internally, no additional copy + /// is made. + /// + /// \param data The QByteArray to copy the data from + explicit IndexedName(const QByteArray & data) : type(""), index(0) { set(data.constData(), data.size()); } - IndexedName(const IndexedName &other) - : type(other.type), index(other.index) - {} - + /// Given constant name and an index, re-use the existing memory for the name, not making a copy + /// of it, or scanning any existing storage for it. The name must never become invalid for the + /// lifetime of the object it names. This memory will never be re-used by another object. + /// + /// \param name The name of the object. This memory is NOT copied and must be persistent. + /// \param index A positive, non-zero integer + /// \return An IndexedName with the given name and index, re-using the existing memory for name static IndexedName fromConst(const char *name, int index) { + assert (index >= 0); IndexedName res; res.type = name; res.index = index; return res; } - IndexedName & operator=(const IndexedName & other) + /// Given an existing std::string, *append* this name to it. If index is not zero, this will + /// include the index. + /// + /// \param buffer A (possibly non-empty) string buffer to append the name to. + void appendToStringBuffer(std::string & buffer) const { - this->index = other.index; - this->type = other.type; - return *this; + buffer += this->type; + if (this->index > 0) { + buffer += std::to_string(this->index); + } } - friend std::ostream & operator<<(std::ostream & s, const IndexedName & e) + /// Create and return a new std::string with this name in it. + /// + /// \return A newly-created string with the IndexedName in it (e.g. "EDGE42") + std::string toString() const { - s << e.type; - if (e.index > 0) - s << e.index; - return s; + std::string result; + this->appendToStringBuffer(result); + return result; } + /// An indexedName is represented as the simple concatenation of the name and its index, e.g. + /// "EDGE1" or "FACE42". + friend std::ostream & operator<<(std::ostream & stream, const IndexedName & indexedName) + { + stream << indexedName.type; + if (indexedName.index > 0) { + stream << indexedName.index; + } + return stream; + } + + /// True only if both the name and index compare exactly equal. bool operator==(const IndexedName & other) const { return this->index == other.index && (this->type == other.type - || std::strcmp(this->type, other.type)==0); + || std::strcmp(this->type, other.type)==0); } + /// Increments the index by the given offset. Does not affect the text part of the name. IndexedName & operator+=(int offset) { this->index += offset; @@ -105,12 +174,15 @@ public: return *this; } + /// Pre-increment operator: increases the index of this element by one. IndexedName & operator++() { ++this->index; return *this; } + /// Pre-decrement operator: decreases the index of this element by one. Must not make the index + /// negative (only checked when compiled in debug mode). IndexedName & operator--() { --this->index; @@ -118,57 +190,83 @@ public: return *this; } + /// True if either the name or the index compare not equal. bool operator!=(const IndexedName & other) const { return !(this->operator==(other)); } - const char * toString(std::string & s) const - { - // Note! s is not cleared on purpose. - std::size_t offset = s.size(); - s += this->type; - if (this->index > 0) - s += std::to_string(this->index); - return s.c_str() + offset; - } - + /// Equivalent to C++20's operator <=> int compare(const IndexedName & other) const { - int res = std::strcmp(this->type, other.type); - if (res) + int res = std::strcmp(this->type, other.type); + if (res != 0) { return res; - if (this->index < other.index) + } + if (this->index < other.index) { return -1; - if (this->index > other.index) + } + if (this->index > other.index) { return 1; + } return 0; } + /// Provided to enable sorting operations: the comparison is first lexicographical for the text + /// element of the names, then numerical for the indices. bool operator<(const IndexedName & other) const { return compare(other) < 0; } - char operator[](int index) const - { - return this->type[index]; - } + /// Allow direct memory access to the individual characters of the text portion of the name. + /// NOTE: input is not range-checked when compiled in release mode. + char operator[](int input) const + { + assert(input >= 0); + assert(input < static_cast(std::strlen(this->type))); + // When we support C++20 we can use std::span<> to eliminate the clang-tidy warning + // NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic + return this->type[input]; + } - const char * getType() const { return this->type; } + /// Get a pointer to text part of the name - does NOT make a copy, returns direct memory access + const char * getType() const { return this->type; } - int getIndex() const { return this->index; } + /// Get the numerical part of the name + int getIndex() const { return this->index; } - void setIndex(int index) { assert(index>=0); this->index = index; } + /// Set the numerical part of the name (note that there is no equivalent function to allow + /// changing the text part of the name, which is immutable once created). + /// + /// \param input The new index. Must be a positive non-zero integer + void setIndex(int input) { assert(input>=0); this->index = input; } - bool isNull() const { return !this->type[0]; } + /// A name is considered "null" if its text component is an empty string. + // When we support C++20 we can use std::span<> to eliminate the clang-tidy warning + // NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic + bool isNull() const { return this->type[0] == '\0'; } + /// Boolean conversion provides the opposite of isNull(), yielding true when the text part of + /// the name is NOT the empty string. explicit operator bool() const { return !isNull(); } protected: - void set(const char *, - int len = -1, - const std::vector &types = {}, + /// Apply the IndexedName rules and either store the characters of a new type or a reference to + /// the characters in a type named in types, or stored statically within this function. If len + /// is not set, or set to -1 (the default), then the provided string in name is scanned for its + /// length using strlen (e.g. it must be null-terminated). + /// + /// \param name The new name. If necessary a copy is made, this char * need not be persistent + /// \param length The length of name + /// \param allowedNames A vector of storage locations of allowed names. These storage locations + /// must be persistent for the duration of the program run. + /// \param allowOthers If true (the default), then if name is not in allowedNames it is allowed, + /// and it is added to internal storage (making a copy of the name if this is its first + /// occurrence). + void set(const char *name, + int length = -1, + const std::vector & allowedNames = {}, bool allowOthers = true); private: @@ -176,6 +274,65 @@ private: int index; }; + +/// A thin wrapper around a QByteArray providing the ability to force a copy of the data at any +/// time, even if it isn't being written to. The standard assignment operator for this class *does* +/// make a copy of the data, unlike the standard assignment operator for QByteArray. +struct ByteArray +{ + explicit ByteArray(QByteArray other) + :bytes(std::move(other)) + {} + + ByteArray(const ByteArray& other) = default; + + ByteArray(ByteArray&& other) noexcept + :bytes(std::move(other.bytes)) + {} + + ~ByteArray() = default; + + /// Guarantee that the stored QByteArray does not share its memory with another instance. + void ensureUnshared() const + { + QByteArray copy; + copy.append(bytes.constData(), bytes.size()); + bytes = copy; + } + + bool operator==(const ByteArray& other) const { + return bytes == other.bytes; + } + + ByteArray &operator=(const ByteArray & other) { + bytes.clear(); + bytes.append(other.bytes.constData(), other.bytes.size()); + return *this; + } + + ByteArray &operator= (ByteArray&& other) noexcept + { + bytes = std::move(other.bytes); + return *this; + } + + mutable QByteArray bytes; +}; + + +struct ByteArrayHasher +{ + std::size_t operator()(const ByteArray& bytes) const + { + return qHash(bytes.bytes); + } + + std::size_t operator()(const QByteArray& bytes) const + { + return qHash(bytes); + } +}; + } -#endif _IndexedName_h_ +#endif // APP_INDEXEDNAME_H diff --git a/tests/src/App/CMakeLists.txt b/tests/src/App/CMakeLists.txt index 0b4eaed5eb..fbde1bb2fa 100644 --- a/tests/src/App/CMakeLists.txt +++ b/tests/src/App/CMakeLists.txt @@ -3,6 +3,7 @@ target_sources( PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Branding.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Expression.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/IndexedName.cpp ${CMAKE_CURRENT_SOURCE_DIR}/License.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Metadata.cpp ) diff --git a/tests/src/App/IndexedName.cpp b/tests/src/App/IndexedName.cpp new file mode 100644 index 0000000000..914fb4f433 --- /dev/null +++ b/tests/src/App/IndexedName.cpp @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" + +#include "App/IndexedName.h" + +#include + +// NOLINTBEGIN(readability-magic-numbers) + +class IndexedNameTest : public ::testing::Test { +protected: + // void SetUp() override {} + + // void TearDown() override {} + + // Create and return a list of invalid IndexedNames + static std::vector givenInvalidIndexedNames() { + return std::vector { + Data::IndexedName(), + Data::IndexedName("",1), + Data::IndexedName("INVALID42NAME",1), + Data::IndexedName(".EDGE",1) + }; + } + + // Create and return a list of valid IndexedNames + static std::vector givenValidIndexedNames() { + return std::vector { + Data::IndexedName("NAME"), + Data::IndexedName("NAME1"), + Data::IndexedName("NAME",1), + Data::IndexedName("NAME_WITH_UNDERSCORES12345") + }; + } + + // An arbitrary list of C strings used for testing some types of construction + // NOLINTNEXTLINE cppcoreguidelines-non-private-member-variables-in-classes + std::vector allowedTypes { + "VERTEX", + "EDGE", + "FACE", + "WIRE" + }; +}; + +TEST_F(IndexedNameTest, defaultConstruction) +{ + // Act + auto indexedName = Data::IndexedName(); + + // Assert + EXPECT_STREQ(indexedName.getType(), ""); + EXPECT_EQ(indexedName.getIndex(), 0); +} + +TEST_F(IndexedNameTest, nameOnlyConstruction) +{ + // Act + auto indexedName = Data::IndexedName("TestName"); + + // Assert + EXPECT_STREQ(indexedName.getType(), "TestName"); + EXPECT_EQ(indexedName.getIndex(), 0); +} + +TEST_F(IndexedNameTest, nameAndIndexConstruction) +{ + // Arrange + const int testIndex {42}; + + // Act + auto indexedName = Data::IndexedName("TestName", testIndex); + + // Assert + EXPECT_STREQ(indexedName.getType(), "TestName"); + EXPECT_EQ(indexedName.getIndex(), testIndex); +} + +TEST_F(IndexedNameTest, nameAndIndexConstructionWithOverride) +{ + // Arrange + const int testIndex {42}; + + // Act + auto indexedName = Data::IndexedName("TestName17", testIndex); + + // Assert + EXPECT_STREQ(indexedName.getType(), "TestName"); + EXPECT_EQ(indexedName.getIndex(), testIndex); +} + +// Names must only contain ASCII letters and underscores (but may end with a number) +TEST_F(IndexedNameTest, constructionInvalidCharInName) +{ + // Arrange + constexpr int lastASCIICode{127}; + std::vector illegalCharacters = {}; + for (int code = 1; code <= lastASCIICode; ++code) { + if ((std::isalnum(code) == 0) && code != '_') { + illegalCharacters.push_back(char(code)); + } + } + for (auto illegalChar : illegalCharacters) { + std::string testName {"TestName"}; + testName += illegalChar; + + // Act + auto indexedName = Data::IndexedName(testName.c_str(), 1); + + // Assert + EXPECT_STREQ(indexedName.getType(), "") << "Expected empty name when given " << testName; + } +} + +// Names must not contain numbers in the middle: +TEST_F(IndexedNameTest, constructionNumberInName) +{ + // Arrange + const int testIndex {42}; + std::string testName; + testName += "Test" + std::to_string(testIndex) + "Name"; + + // Act + auto indexedName = Data::IndexedName(testName.c_str(), testIndex); + + // Assert + EXPECT_STREQ(indexedName.getType(), ""); +} + +TEST_F(IndexedNameTest, nameAndTypeListConstructionWithoutAllowOthers) +{ + // Act + auto indexedName = Data::IndexedName("EDGE19", allowedTypes, false); + + // Assert + EXPECT_STREQ(indexedName.getType(), "EDGE"); + EXPECT_EQ(indexedName.getIndex(), 19); + + // Act + indexedName = Data::IndexedName("EDGES_ARE_REALLY_GREAT19", allowedTypes, false); + + // Assert + EXPECT_STREQ(indexedName.getType(), ""); + EXPECT_EQ(indexedName.getIndex(), 19); + + // Act + indexedName = Data::IndexedName("NOT_IN_THE_LIST42", allowedTypes, false); + + // Assert + EXPECT_STREQ(indexedName.getType(), ""); +} + +TEST_F(IndexedNameTest, nameAndTypeListConstructionWithAllowOthers) +{ + // Act + auto indexedName = Data::IndexedName("NOT_IN_THE_LIST42", allowedTypes, true); + + // Assert + EXPECT_STREQ(indexedName.getType(), "NOT_IN_THE_LIST"); + EXPECT_EQ(indexedName.getIndex(), 42); +} + +// Check that the same memory location is used for two names that are not in the allowedTypes list +TEST_F(IndexedNameTest, nameAndTypeListConstructionReusedMemoryCheck) +{ + // Arrange + auto indexedName1 = Data::IndexedName("NOT_IN_THE_LIST42", allowedTypes, true); + auto indexedName2 = Data::IndexedName("NOT_IN_THE_LIST43", allowedTypes, true); + + // Act & Assert + EXPECT_EQ(indexedName1.getType(), indexedName2.getType()); +} + +TEST_F(IndexedNameTest, byteArrayConstruction) +{ + // Arrange + QByteArray qba{"EDGE42"}; + + // Act + auto indexedName = Data::IndexedName(qba); + + // Assert + EXPECT_STREQ(indexedName.getType(), "EDGE"); + EXPECT_EQ(indexedName.getIndex(), 42); +} + +TEST_F(IndexedNameTest, copyConstruction) +{ + // Arrange + auto indexedName = Data::IndexedName("EDGE42"); + + // Act + auto indexedNameCopy {indexedName}; + + // Assert + EXPECT_EQ(indexedName, indexedNameCopy); +} + +TEST_F(IndexedNameTest, streamInsertionOperator) +{ + // Arrange + auto indexedName = Data::IndexedName("EDGE42"); + std::stringstream ss; + + // Act + ss << indexedName; + + // Assert + EXPECT_EQ(ss.str(), std::string{"EDGE42"}); +} + +TEST_F(IndexedNameTest, compoundAssignmentOperator) +{ + // NOTE: Only += is defined for this class + + // Arrange + constexpr int base{42}; + constexpr int offset{10}; + auto indexedName = Data::IndexedName("EDGE",base); + + // Act + indexedName += offset; + + // Assert + EXPECT_EQ(indexedName.getIndex(), 52); +} + +TEST_F(IndexedNameTest, preincrementOperator) +{ + // Arrange + auto indexedName = Data::IndexedName("EDGE42"); + + // Act + ++indexedName; + + // Assert + EXPECT_EQ(indexedName.getIndex(), 43); +} + +TEST_F(IndexedNameTest, predecrementOperator) +{ + // Arrange + auto indexedName = Data::IndexedName("EDGE42"); + + // Act + --indexedName; + + // Assert + EXPECT_EQ(indexedName.getIndex(), 41); +} + +TEST_F(IndexedNameTest, comparisonOperators) +{ + // Arrange + auto indexedName1 = Data::IndexedName("EDGE42"); + auto indexedName2 = Data::IndexedName("EDGE42"); + + // Act & Assert + EXPECT_EQ(indexedName1.compare(indexedName2), 0); + EXPECT_TRUE(indexedName1 == indexedName2); + EXPECT_FALSE(indexedName1 != indexedName2); + EXPECT_FALSE(indexedName1 < indexedName2); + + // Arrange + auto indexedName3 = Data::IndexedName("EDGE42"); + auto indexedName4 = Data::IndexedName("FACE42"); + + // Act & Assert + EXPECT_EQ(indexedName3.compare(indexedName4), -1); + EXPECT_FALSE(indexedName3 == indexedName4); + EXPECT_TRUE(indexedName3 != indexedName4); + EXPECT_TRUE(indexedName3 < indexedName4); + + // Arrange + auto indexedName5 = Data::IndexedName("FACE42"); + auto indexedName6 = Data::IndexedName("EDGE42"); + + // Act & Assert + EXPECT_EQ(indexedName5.compare(indexedName6), 1); + EXPECT_FALSE(indexedName5 == indexedName6); + EXPECT_TRUE(indexedName5 != indexedName6); + EXPECT_FALSE(indexedName5 < indexedName6); + + // Arrange + auto indexedName7 = Data::IndexedName("EDGE41"); + auto indexedName8 = Data::IndexedName("EDGE42"); + + // Act & Assert + EXPECT_EQ(indexedName7.compare(indexedName8), -1); + EXPECT_FALSE(indexedName7 == indexedName8); + EXPECT_TRUE(indexedName7 != indexedName8); + EXPECT_TRUE(indexedName7 < indexedName8); + + // Arrange + auto indexedName9 = Data::IndexedName("EDGE43"); + auto indexedName10 = Data::IndexedName("EDGE42"); + + // Act & Assert + EXPECT_EQ(indexedName9.compare(indexedName10), 1); + EXPECT_FALSE(indexedName9 == indexedName10); + EXPECT_TRUE(indexedName9 != indexedName10); + EXPECT_FALSE(indexedName9 < indexedName10); +} + +TEST_F(IndexedNameTest, subscriptOperator) +{ + // Arrange + auto indexedName = Data::IndexedName("EDGE42"); + + // Act & Assert + EXPECT_EQ(indexedName[0], 'E'); + EXPECT_EQ(indexedName[1], 'D'); + EXPECT_EQ(indexedName[2], 'G'); + EXPECT_EQ(indexedName[3], 'E'); +} + +TEST_F(IndexedNameTest, getType) +{ + // Arrange + auto indexedName = Data::IndexedName("EDGE42"); + + // Act & Assert + EXPECT_STREQ(indexedName.getType(), "EDGE"); +} + +TEST_F(IndexedNameTest, setIndex) +{ + // Arrange + auto indexedName = Data::IndexedName("EDGE42"); + EXPECT_EQ(indexedName.getIndex(), 42); + + // Act + indexedName.setIndex(1); + + // Assert + EXPECT_EQ(indexedName.getIndex(), 1); +} + +TEST_F(IndexedNameTest, isNullTrue) +{ + // Arrange + auto invalidNames = givenInvalidIndexedNames(); + for (const auto &name : invalidNames) { + + // Act & Assert + EXPECT_TRUE(name.isNull()); + } +} + +TEST_F(IndexedNameTest, isNullFalse) +{ + // Arrange + auto validNames = givenValidIndexedNames(); + for (const auto &name : validNames) { + + // Act & Assert + EXPECT_FALSE(name.isNull()); + } +} + +TEST_F(IndexedNameTest, booleanConversionFalse) +{ + // Arrange + auto invalidNames = givenInvalidIndexedNames(); + for (const auto &name : invalidNames) { + + // Act & Assert + EXPECT_FALSE(static_cast(name)); + } + + // Usage example: + auto indexedName = Data::IndexedName(".EDGE",1); // Invalid name + if (indexedName) { + FAIL() << "indexedName as a boolean should have been false for an invalid name"; + } +} + +TEST_F(IndexedNameTest, booleanConversionTrue) +{ + // Arrange + auto validNames = givenValidIndexedNames(); + for (const auto& name : validNames) { + + // Act & Assert + EXPECT_TRUE(static_cast(name)); + } +} + +TEST_F(IndexedNameTest, fromConst) +{ + // Arrange + const int testIndex {42}; + + // Act + auto indexedName = Data::IndexedName::fromConst("TestName", testIndex); + + // Assert + EXPECT_STREQ(indexedName.getType(), "TestName"); + EXPECT_EQ(indexedName.getIndex(), testIndex); +} + +TEST_F(IndexedNameTest, appendToStringBufferEmptyBuffer) +{ + // Arrange + std::string bufferStartedEmpty; + Data::IndexedName testName("TEST_NAME", 1); + + // Act + testName.appendToStringBuffer(bufferStartedEmpty); + + // Assert + EXPECT_EQ(bufferStartedEmpty, "TEST_NAME1"); +} + +TEST_F(IndexedNameTest, appendToStringBufferNonEmptyBuffer) +{ + // Arrange + std::string bufferWithData {"DATA"}; + Data::IndexedName testName("TEST_NAME", 1); + + // Act + testName.appendToStringBuffer(bufferWithData); + + // Assert + EXPECT_EQ(bufferWithData, "DATATEST_NAME1"); +} + +TEST_F(IndexedNameTest, appendToStringBufferZeroIndex) +{ + // Arrange + std::string bufferStartedEmpty; + Data::IndexedName testName("TEST_NAME", 0); + + // Act + testName.appendToStringBuffer(bufferStartedEmpty); + + // Assert + EXPECT_EQ(bufferStartedEmpty, "TEST_NAME"); +} + +TEST_F(IndexedNameTest, toString) +{ + // Arrange + Data::IndexedName testName("TEST_NAME", 1); + + // Act + auto result = testName.toString(); + + // Assert + EXPECT_EQ(result, "TEST_NAME1"); +} + +TEST_F(IndexedNameTest, toStringNoIndex) +{ + // Arrange + Data::IndexedName testName("TEST_NAME", 0); + + // Act + auto result = testName.toString(); + + // Assert + EXPECT_EQ(result, "TEST_NAME"); +} + +TEST_F(IndexedNameTest, assignmentOperator) +{ + // Arrange + const int testIndex1 {42}; + const int testIndex2 {24}; + auto indexedName1 = Data::IndexedName::fromConst("TestName", testIndex1); + auto indexedName2 = Data::IndexedName::fromConst("TestName2", testIndex2); + EXPECT_NE(indexedName1, indexedName2); // Ensure the test is set up correctly + + // Act + indexedName1 = indexedName2; + + // Assert + EXPECT_EQ(indexedName1, indexedName2); +} + + +class ByteArrayTest : public ::testing::Test { +protected: + // void SetUp() override {} + + // void TearDown() override {} +}; + +TEST_F(ByteArrayTest, QByteArrayConstruction) +{ + // Arrange + QByteArray testQBA("Data in a QByteArray"); + + // Act + Data::ByteArray testByteArray (testQBA); + + // Assert + EXPECT_EQ(testQBA, testByteArray.bytes); +} + +TEST_F(ByteArrayTest, CopyConstruction) +{ + // Arrange + QByteArray testQBA("Data in a QByteArray"); + Data::ByteArray originalByteArray (testQBA); + + // Act + // NOLINTNEXTLINE performance-unnecessary-copy-initialization + Data::ByteArray copiedByteArray (originalByteArray); + + // Assert + EXPECT_EQ(originalByteArray, copiedByteArray); +} + +TEST_F(ByteArrayTest, MoveConstruction) +{ + // Arrange + QByteArray testQBA("Data in a QByteArray"); + Data::ByteArray originalByteArray (testQBA); + const auto *originalDataLocation = originalByteArray.bytes.constData(); + + // Act + Data::ByteArray copiedByteArray (std::move(originalByteArray)); + + // Assert + EXPECT_EQ(testQBA, copiedByteArray.bytes); + EXPECT_EQ(originalDataLocation, copiedByteArray.bytes.constData()); +} + +TEST_F(ByteArrayTest, ensureUnshared) +{ + // Arrange + QByteArray testQBA("Data in a QByteArray"); + Data::ByteArray originalByteArray (testQBA); + const auto *originalDataLocation = originalByteArray.bytes.constData(); + Data::ByteArray copiedByteArray (originalByteArray); + + // Act + copiedByteArray.ensureUnshared(); + + // Assert + EXPECT_EQ(testQBA, copiedByteArray.bytes); + EXPECT_NE(originalDataLocation, copiedByteArray.bytes.constData()); +} + +TEST_F(ByteArrayTest, equalityOperator) +{ + // Arrange + QByteArray testQBA1("Data in a QByteArray"); + QByteArray testQBA2("Data in a QByteArray"); + QByteArray testQBA3("Not the same data in a QByteArray"); + Data::ByteArray byteArray1 (testQBA1); + Data::ByteArray byteArray2 (testQBA2); + Data::ByteArray byteArray3 (testQBA3); + + // Act & Assert + EXPECT_TRUE(byteArray1 == byteArray2); + EXPECT_FALSE(byteArray1 == byteArray3); +} + +TEST_F(ByteArrayTest, assignmentOperator) +{ + // Arrange + QByteArray testQBA1("Data in a QByteArray"); + QByteArray testQBA2("Different data in a QByteArray"); + Data::ByteArray originalByteArray (testQBA1); + Data::ByteArray newByteArray (testQBA2); + ASSERT_FALSE(originalByteArray == newByteArray); + + // Act + newByteArray = originalByteArray; + + // Assert + EXPECT_TRUE(originalByteArray == newByteArray); +} + +TEST_F(ByteArrayTest, moveAssignmentOperator) +{ + // Arrange + QByteArray testQBA1("Data in a QByteArray"); + QByteArray testQBA2("Different data in a QByteArray"); + Data::ByteArray originalByteArray (testQBA1); + const auto *originalByteArrayLocation = originalByteArray.bytes.constData(); + Data::ByteArray newByteArray (testQBA2); + ASSERT_FALSE(originalByteArray == newByteArray); + + // Act + newByteArray = std::move(originalByteArray); + + // Assert + EXPECT_EQ(originalByteArrayLocation, newByteArray.bytes.constData()); +} + +// NOLINTEND(readability-magic-numbers) From 5f3b86e87cdc026ed85799b7986a68d27a42132a Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 14 Mar 2023 12:54:48 -0500 Subject: [PATCH 09/12] Addon Manager: Fix #8833 --- src/Mod/AddonManager/addonmanager_update_all_gui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/AddonManager/addonmanager_update_all_gui.py b/src/Mod/AddonManager/addonmanager_update_all_gui.py index b458dc947e..59d59c7f7f 100644 --- a/src/Mod/AddonManager/addonmanager_update_all_gui.py +++ b/src/Mod/AddonManager/addonmanager_update_all_gui.py @@ -194,12 +194,14 @@ class UpdateAllGUI(QtCore.QObject): text = translate("Addons installer", "Finished updating the following addons") self._set_dialog_to_final_state(text) self.running = False + self.finished.emit() def _setup_cancelled_state(self): text1 = translate("AddonsInstaller", "Update was cancelled") text2 = translate("AddonsInstaller", "some addons may have been updated") self._set_dialog_to_final_state(text1 + ": " + text2) self.running = False + self.finished.emit() def _set_dialog_to_final_state(self,new_content): self.dialog.buttonBox.clear() From ad1b5c7ff6082b59a997a96cb6490a63952ffd50 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 14 Mar 2023 12:57:59 -0500 Subject: [PATCH 10/12] Addon Manager: Pylint cleanup --- .../AddonManager/addonmanager_update_all_gui.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Mod/AddonManager/addonmanager_update_all_gui.py b/src/Mod/AddonManager/addonmanager_update_all_gui.py index 59d59c7f7f..3c2b09ffc8 100644 --- a/src/Mod/AddonManager/addonmanager_update_all_gui.py +++ b/src/Mod/AddonManager/addonmanager_update_all_gui.py @@ -42,16 +42,17 @@ translate = FreeCAD.Qt.translate class UpdaterFactory: - """A factory class for generating updaters. Mainly exists to allow eaily mocking those - updaters during testing. A replacement class need only provide a "get_updater" function - that returns mock updater objects. Those objects must be QObjects with a run() function - and a finished signal.""" + """A factory class for generating updaters. Mainly exists to allow easily mocking + those updaters during testing. A replacement class need only provide a + "get_updater" function that returns mock updater objects. Those objects must be + QObjects with a run() function and a finished signal.""" def __init__(self, addons): self.addons = addons def get_updater(self, addon): - """Get an updater for this addon (either a MacroInstaller or an AddonInstaller)""" + """Get an updater for this addon (either a MacroInstaller or an + AddonInstaller)""" if addon.macro is not None: return MacroInstaller(addon) return AddonInstaller(addon, self.addons) @@ -101,7 +102,8 @@ class UpdateAllGUI(QtCore.QObject): self.cancelled = False def run(self): - """Run the Update All process. Blocks until updates are complete or cancelled.""" + """Run the Update All process. Blocks until updates are complete or + cancelled.""" self.running = True self._setup_dialog() self.dialog.show() @@ -203,7 +205,7 @@ class UpdateAllGUI(QtCore.QObject): self.running = False self.finished.emit() - def _set_dialog_to_final_state(self,new_content): + def _set_dialog_to_final_state(self, new_content): self.dialog.buttonBox.clear() self.dialog.buttonBox.addButton(QtWidgets.QDialogButtonBox.Close) self.dialog.label.setText(new_content) From 38a100962c33ec048a8c3342052ca6894659398b Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 14 Mar 2023 21:46:49 +0100 Subject: [PATCH 11/12] Import/Part: [skip ci] restore some comments --- src/Mod/Import/App/AppImportPy.cpp | 2 ++ src/Mod/Import/App/ImportOCAF2.cpp | 5 ++++- src/Mod/Import/Gui/AppImportGuiPy.cpp | 2 ++ src/Mod/Part/App/TopoShape.cpp | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Mod/Import/App/AppImportPy.cpp b/src/Mod/Import/App/AppImportPy.cpp index 9b7b5118c8..b18a995e63 100644 --- a/src/Mod/Import/App/AppImportPy.cpp +++ b/src/Mod/Import/App/AppImportPy.cpp @@ -341,6 +341,8 @@ private: Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/Part")->GetGroup("STEP"); + // Don't set name because STEP doesn't support UTF-8 + // https://forum.freecadweb.org/viewtopic.php?f=8&t=52967 makeHeader.SetAuthorValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Author", "Author").c_str())); makeHeader.SetOrganizationValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Company").c_str())); makeHeader.SetOriginatingSystem(new TCollection_HAsciiString(App::Application::getExecutableName().c_str())); diff --git a/src/Mod/Import/App/ImportOCAF2.cpp b/src/Mod/Import/App/ImportOCAF2.cpp index fde27edcd2..61611b0107 100644 --- a/src/Mod/Import/App/ImportOCAF2.cpp +++ b/src/Mod/Import/App/ImportOCAF2.cpp @@ -1175,8 +1175,11 @@ void ExportOCAF2::exportObjects(std::vector &objs, const c setName(label,nullptr,name); } - if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { dumpLabels(pDoc->Main(),aShapeTool,aColorTool); + } + + // Update is not performed automatically anymore: https://tracker.dev.opencascade.org/view.php?id=28055 aShapeTool->UpdateAssemblies(); } diff --git a/src/Mod/Import/Gui/AppImportGuiPy.cpp b/src/Mod/Import/Gui/AppImportGuiPy.cpp index 4d0e1c2f4f..b506b80a53 100644 --- a/src/Mod/Import/Gui/AppImportGuiPy.cpp +++ b/src/Mod/Import/Gui/AppImportGuiPy.cpp @@ -669,6 +669,8 @@ private: Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/Part")->GetGroup("STEP"); + // Don't set name because STEP doesn't support UTF-8 + // https://forum.freecadweb.org/viewtopic.php?f=8&t=52967 makeHeader.SetAuthorValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Author", "Author").c_str())); makeHeader.SetOrganizationValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Company").c_str())); makeHeader.SetOriginatingSystem(new TCollection_HAsciiString(App::Application::getExecutableName().c_str())); diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 92018770f1..8ee78793ce 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -952,8 +952,8 @@ void TopoShape::exportStep(const char *filename) const throw Base::FileException("Error in transferring STEP"); APIHeaderSection_MakeHeader makeHeader(aWriter.Model()); + // Don't set name because STEP doesn't support UTF-8 // https://forum.freecadweb.org/viewtopic.php?f=8&t=52967 - //makeHeader.SetName(new TCollection_HAsciiString((Standard_CString)(encodeFilename(filename).c_str()))); makeHeader.SetAuthorValue (1, new TCollection_HAsciiString("FreeCAD")); makeHeader.SetOrganizationValue (1, new TCollection_HAsciiString("FreeCAD")); makeHeader.SetOriginatingSystem(new TCollection_HAsciiString("FreeCAD")); From 6345ae3d7a60ecfda1d975cd4ed174e0cdaed285 Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 14 Mar 2023 21:39:31 +0100 Subject: [PATCH 12/12] MSYS2: fix creation of ProgressIndicatorPy --- src/Base/ProgressIndicatorPy.cpp | 15 +++++++++++++++ src/Base/ProgressIndicatorPy.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/src/Base/ProgressIndicatorPy.cpp b/src/Base/ProgressIndicatorPy.cpp index a3a4a47405..a5c0dfcfe2 100644 --- a/src/Base/ProgressIndicatorPy.cpp +++ b/src/Base/ProgressIndicatorPy.cpp @@ -43,6 +43,21 @@ void ProgressIndicatorPy::init_type() add_varargs_method("stop",&ProgressIndicatorPy::stop,"stop()"); } +Py::PythonType& ProgressIndicatorPy::behaviors() +{ + return Py::PythonExtension::behaviors(); +} + +PyTypeObject* ProgressIndicatorPy::type_object() +{ + return Py::PythonExtension::type_object(); +} + +bool ProgressIndicatorPy::check(PyObject* p) +{ + return Py::PythonExtension::check(p); +} + PyObject *ProgressIndicatorPy::PyMake(struct _typeobject *, PyObject *, PyObject *) { return new ProgressIndicatorPy(); diff --git a/src/Base/ProgressIndicatorPy.h b/src/Base/ProgressIndicatorPy.h index 556fc97bdc..249068b02f 100644 --- a/src/Base/ProgressIndicatorPy.h +++ b/src/Base/ProgressIndicatorPy.h @@ -35,6 +35,9 @@ class BaseExport ProgressIndicatorPy : public Py::PythonExtension