Files
create/src/Mod/Sketcher/Gui/TaskSketcherConstraints.cpp
Markus Reitböck 45c02f7978 Sketcher: use CMake to generate precompiled headers on all platforms
"Professional CMake" book suggest the following:

"Targets should build successfully with or without compiler support for precompiled headers. It
 should be considered an optimization, not a requirement. In particular, do not explicitly include a
 precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically
 generated precompile header on the compiler command line instead. This is more portable across
 the major compilers and is likely to be easier to maintain. It will also avoid warnings being
 generated from certain code checking tools like iwyu (include what you use)."

Therefore, removed the "#include <PreCompiled.h>" from sources, also
there is no need for the "#ifdef _PreComp_" anymore
2025-09-23 00:51:00 +02:00

1799 lines
72 KiB
C++

/***************************************************************************
* Copyright (c) 2009 Juergen Riegel <juergen.riegel@web.de> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include <QContextMenuEvent>
#include <QMenu>
#include <QPainter>
#include <QPixmapCache>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QString>
#include <QStyledItemDelegate>
#include <QWidgetAction>
#include <boost/core/ignore_unused.hpp>
#include <cmath>
#include <limits>
#include <App/Application.h>
#include <App/Document.h>
#include <App/Expression.h>
#include <Gui/Application.h>
#include <Gui/BitmapFactory.h>
#include <Gui/CommandT.h>
#include <Gui/Document.h>
#include <Gui/MainWindow.h>
#include <Gui/Notifications.h>
#include <Gui/Selection/Selection.h>
#include <Gui/Selection/SelectionObject.h>
#include <Gui/ViewProvider.h>
#include <Mod/Sketcher/App/SketchObject.h>
#include "EditDatumDialog.h"
#include "TaskSketcherConstraints.h"
#include "Utils.h"
#include "ViewProviderSketch.h"
#include "ui_TaskSketcherConstraints.h"
// clang-format off
using namespace SketcherGui;
using namespace Gui::TaskView;
namespace sp = std::placeholders;
// Translation block for context menu: do not remove
#if 0
QT_TRANSLATE_NOOP("SketcherGui::ConstraintView", "Select Elements");
#endif
/// Inserts a QAction into an existing menu
/// ICONSTR is the string of the icon in the resource file
/// NAMESTR is the text appearing in the contextual menuAction
/// CMDSTR is the string registered in the commandManager
/// FUNC is the name of the member function to be executed on selection of the menu item
/// ACTSONSELECTION is a true/false value to activate the command only if a selection is made
#define CONTEXT_ITEM(ICONSTR, NAMESTR, CMDSTR, FUNC, ACTSONSELECTION) \
QIcon icon_##FUNC(Gui::BitmapFactory().pixmap(ICONSTR)); \
QAction* constr_##FUNC = menu.addAction(icon_##FUNC, tr(NAMESTR), this, SLOT(FUNC())); \
constr_##FUNC->setShortcut(QKeySequence(QString::fromUtf8( \
Gui::Application::Instance->commandManager().getCommandByName(CMDSTR)->getAccel()))); \
if (ACTSONSELECTION) \
constr_##FUNC->setEnabled(!items.isEmpty()); \
else \
constr_##FUNC->setEnabled(true);
/// Defines the member function corresponding to the CONTEXT_ITEM macro
#define CONTEXT_MEMBER_DEF(CMDSTR, FUNC) \
void ConstraintView::FUNC() \
{ \
Gui::Application::Instance->commandManager().runCommandByName(CMDSTR); \
}
// helper class to store additional information about the listWidget entry.
class ConstraintItem: public QListWidgetItem
{
public:
ConstraintItem(const Sketcher::SketchObject* s, ViewProviderSketch* sketchview, int ConstNbr)
: QListWidgetItem(QString())
, sketch(s)
, sketchView(sketchview)
, ConstraintNbr(ConstNbr)
{
this->setFlags(this->flags() | Qt::ItemIsEditable | Qt::ItemIsUserCheckable);
updateVirtualSpaceStatus();
}
~ConstraintItem() override
{}
void setData(int role, const QVariant& value) override
{
if (role == Qt::EditRole)
this->value = value;
QListWidgetItem::setData(role, value);
}
QVariant data(int role) const override
{
if (ConstraintNbr < 0 || ConstraintNbr >= sketch->Constraints.getSize())
return QVariant();
const Sketcher::Constraint* constraint = sketch->Constraints[ConstraintNbr];
// it can happen that the geometry of the sketch is tmp. invalid and thus
// the index operator returns null.
if (!constraint) {
return QVariant();
}
if (role == Qt::EditRole) {
if (value.isValid())
return value;
else
return QString::fromStdString(
Sketcher::PropertyConstraintList::getConstraintName(constraint->Name,
ConstraintNbr));
}
else if (role == Qt::DisplayRole) {
QString name =
QString::fromStdString(Sketcher::PropertyConstraintList::getConstraintName(
constraint->Name, ConstraintNbr));
switch (constraint->Type) {
case Sketcher::Horizontal:
case Sketcher::Vertical:
case Sketcher::Coincident:
case Sketcher::PointOnObject:
case Sketcher::Parallel:
case Sketcher::Perpendicular:
case Sketcher::Tangent:
case Sketcher::Equal:
case Sketcher::Symmetric:
case Sketcher::Block:
break;
case Sketcher::Distance:
case Sketcher::DistanceX:
case Sketcher::DistanceY:
case Sketcher::Radius:
case Sketcher::Weight:
case Sketcher::Diameter:
case Sketcher::Angle:
name = QStringLiteral("%1 (%2)").arg(
name,
QString::fromStdString(constraint->getPresentationValue().getUserString()));
break;
case Sketcher::SnellsLaw: {
double v = constraint->getPresentationValue().getValue();
double n1 = 1.0;
double n2 = 1.0;
if (fabs(v) >= 1) {
n2 = v;
}
else {
n1 = 1 / v;
}
name = QStringLiteral("%1 (%2/%3)").arg(name).arg(n2).arg(n1);
break;
}
case Sketcher::InternalAlignment:
break;
default:
break;
}
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
bool extended = hGrp->GetBool("ExtendedConstraintInformation", false);
if (extended) {
if (constraint->Second == Sketcher::GeoEnum::GeoUndef) {
name = QStringLiteral("%1 [(%2,%3)]")
.arg(name)
.arg(constraint->First)
.arg(static_cast<int>(constraint->FirstPos));
}
else if (constraint->Third == Sketcher::GeoEnum::GeoUndef) {
name = QStringLiteral("%1 [(%2,%3),(%4,%5)]")
.arg(name)
.arg(constraint->First)
.arg(static_cast<int>(constraint->FirstPos))
.arg(constraint->Second)
.arg(static_cast<int>(constraint->SecondPos));
}
else {
name = QStringLiteral("%1 [(%2,%3),(%4,%5),(%6,%7)]")
.arg(name)
.arg(constraint->First)
.arg(static_cast<int>(constraint->FirstPos))
.arg(constraint->Second)
.arg(static_cast<int>(constraint->SecondPos))
.arg(constraint->Third)
.arg(static_cast<int>(constraint->ThirdPos));
}
}
return name;
}
else if (role == Qt::DecorationRole) {
static QIcon hdist(Gui::BitmapFactory().iconFromTheme("Constraint_HorizontalDistance"));
static QIcon vdist(Gui::BitmapFactory().iconFromTheme("Constraint_VerticalDistance"));
static QIcon horiz(Gui::BitmapFactory().iconFromTheme("Constraint_Horizontal"));
static QIcon vert(Gui::BitmapFactory().iconFromTheme("Constraint_Vertical"));
// static QIcon lock ( Gui::BitmapFactory().iconFromTheme("Constraint_Lock") );
static QIcon block(Gui::BitmapFactory().iconFromTheme("Constraint_Block"));
static QIcon coinc(Gui::BitmapFactory().iconFromTheme("Constraint_PointOnPoint"));
static QIcon para(Gui::BitmapFactory().iconFromTheme("Constraint_Parallel"));
static QIcon perp(Gui::BitmapFactory().iconFromTheme("Constraint_Perpendicular"));
static QIcon tang(Gui::BitmapFactory().iconFromTheme("Constraint_Tangent"));
static QIcon dist(Gui::BitmapFactory().iconFromTheme("Constraint_Length"));
static QIcon radi(Gui::BitmapFactory().iconFromTheme("Constraint_Radius"));
static QIcon dia(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter"));
// static QIcon majradi (
// Gui::BitmapFactory().iconFromTheme("Constraint_Ellipse_Major_Radius") ); static QIcon
// minradi ( Gui::BitmapFactory().iconFromTheme("Constraint_Ellipse_Minor_Radius") );
static QIcon angl(Gui::BitmapFactory().iconFromTheme("Constraint_InternalAngle"));
// static QIcon ellipseXUAngl (
// Gui::BitmapFactory().iconFromTheme("Constraint_Ellipse_Axis_Angle") );
static QIcon equal(Gui::BitmapFactory().iconFromTheme("Constraint_EqualLength"));
static QIcon pntoo(Gui::BitmapFactory().iconFromTheme("Constraint_PointOnObject"));
static QIcon symm(Gui::BitmapFactory().iconFromTheme("Constraint_Symmetric"));
static QIcon snell(Gui::BitmapFactory().iconFromTheme("Constraint_SnellsLaw"));
static QIcon iaellipseminoraxis(Gui::BitmapFactory().iconFromTheme(
"Constraint_InternalAlignment_Ellipse_MinorAxis"));
static QIcon iaellipsemajoraxis(Gui::BitmapFactory().iconFromTheme(
"Constraint_InternalAlignment_Ellipse_MajorAxis"));
static QIcon iaellipsefocus1(
Gui::BitmapFactory().iconFromTheme("Constraint_InternalAlignment_Ellipse_Focus1"));
static QIcon iaellipsefocus2(
Gui::BitmapFactory().iconFromTheme("Constraint_InternalAlignment_Ellipse_Focus2"));
static QIcon iaellipseother(
Gui::BitmapFactory().iconFromTheme("Constraint_InternalAlignment"));
static QIcon hdist_driven(
Gui::BitmapFactory().iconFromTheme("Constraint_HorizontalDistance_Driven"));
static QIcon vdist_driven(
Gui::BitmapFactory().iconFromTheme("Constraint_VerticalDistance_Driven"));
static QIcon dist_driven(
Gui::BitmapFactory().iconFromTheme("Constraint_Length_Driven"));
static QIcon radi_driven(
Gui::BitmapFactory().iconFromTheme("Constraint_Radius_Driven"));
static QIcon dia_driven(
Gui::BitmapFactory().iconFromTheme("Constraint_Diameter_Driven"));
static QIcon angl_driven(
Gui::BitmapFactory().iconFromTheme("Constraint_InternalAngle_Driven"));
static QIcon snell_driven(
Gui::BitmapFactory().iconFromTheme("Constraint_SnellsLaw_Driven"));
auto selicon = [this](const Sketcher::Constraint* constr,
const QIcon& normal,
const QIcon& driven) -> QIcon {
if (!constr->isActive) {
QIcon darkIcon;
int w = listWidget()->style()->pixelMetric(QStyle::PM_ListViewIconSize);
darkIcon.addPixmap(normal.pixmap(w, w, QIcon::Disabled, QIcon::Off),
QIcon::Normal,
QIcon::Off);
darkIcon.addPixmap(
normal.pixmap(w, w, QIcon::Disabled, QIcon::On), QIcon::Normal, QIcon::On);
return darkIcon;
}
else if (constr->isDriving) {
return normal;
}
else {
return driven;
}
};
switch (constraint->Type) {
case Sketcher::Horizontal:
return selicon(constraint, horiz, horiz);
case Sketcher::Vertical:
return selicon(constraint, vert, vert);
case Sketcher::Coincident:
return selicon(constraint, coinc, coinc);
case Sketcher::Block:
return selicon(constraint, block, block);
case Sketcher::PointOnObject:
return selicon(constraint, pntoo, pntoo);
case Sketcher::Parallel:
return selicon(constraint, para, para);
case Sketcher::Perpendicular:
return selicon(constraint, perp, perp);
case Sketcher::Tangent:
return selicon(constraint, tang, tang);
case Sketcher::Equal:
return selicon(constraint, equal, equal);
case Sketcher::Symmetric:
return selicon(constraint, symm, symm);
case Sketcher::Distance:
return selicon(constraint, dist, dist_driven);
case Sketcher::DistanceX:
return selicon(constraint, hdist, hdist_driven);
case Sketcher::DistanceY:
return selicon(constraint, vdist, vdist_driven);
case Sketcher::Radius:
case Sketcher::Weight:
return selicon(constraint, radi, radi_driven);
case Sketcher::Diameter:
return selicon(constraint, dia, dia_driven);
case Sketcher::Angle:
return selicon(constraint, angl, angl_driven);
case Sketcher::SnellsLaw:
return selicon(constraint, snell, snell_driven);
case Sketcher::InternalAlignment:
switch (constraint->AlignmentType) {
case Sketcher::EllipseMajorDiameter:
return selicon(constraint, iaellipsemajoraxis, iaellipsemajoraxis);
case Sketcher::EllipseMinorDiameter:
return selicon(constraint, iaellipseminoraxis, iaellipseminoraxis);
case Sketcher::EllipseFocus1:
return selicon(constraint, iaellipsefocus1, iaellipsefocus1);
case Sketcher::EllipseFocus2:
return selicon(constraint, iaellipsefocus2, iaellipsefocus2);
case Sketcher::Undef:
default:
return selicon(constraint, iaellipseother, iaellipseother);
}
default:
return QVariant();
}
}
else if (role == Qt::ToolTipRole) {
App::ObjectIdentifier path = sketch->Constraints.createPath(ConstraintNbr);
App::PropertyExpressionEngine::ExpressionInfo expr_info = sketch->getExpression(path);
if (expr_info.expression)
return QString::fromStdString(expr_info.expression->toString());
else
return QVariant();
}
else
return QListWidgetItem::data(role);
}
Sketcher::ConstraintType constraintType() const
{
assert(ConstraintNbr >= 0 && ConstraintNbr < sketch->Constraints.getSize());
return sketch->Constraints[ConstraintNbr]->Type;
}
bool isEnforceable() const
{
assert(ConstraintNbr >= 0 && ConstraintNbr < sketch->Constraints.getSize());
const Sketcher::Constraint* constraint = sketch->Constraints[ConstraintNbr];
switch (constraint->Type) {
case Sketcher::None:
case Sketcher::NumConstraintTypes:
assert(false);
return false;
case Sketcher::Horizontal:
case Sketcher::Vertical:
case Sketcher::Coincident:
case Sketcher::Block:
case Sketcher::PointOnObject:
case Sketcher::Parallel:
case Sketcher::Perpendicular:
case Sketcher::Tangent:
case Sketcher::Equal:
case Sketcher::Symmetric:
return true;
case Sketcher::Distance:
case Sketcher::DistanceX:
case Sketcher::DistanceY:
case Sketcher::Radius:
case Sketcher::Diameter:
case Sketcher::Weight:
case Sketcher::Angle:
case Sketcher::SnellsLaw:
return (constraint->First >= 0 || constraint->Second >= 0
|| constraint->Third >= 0);
case Sketcher::InternalAlignment:
return true;
}
return false;
}
bool isDimensional() const
{
assert(ConstraintNbr >= 0 && ConstraintNbr < sketch->Constraints.getSize());
return (sketch->Constraints[ConstraintNbr])->isDimensional();
}
bool isDriving() const
{
assert(ConstraintNbr >= 0 && ConstraintNbr < sketch->Constraints.getSize());
return sketch->Constraints[ConstraintNbr]->isDriving;
}
bool isInVirtualSpace() const
{
assert(ConstraintNbr >= 0 && ConstraintNbr < sketch->Constraints.getSize());
return sketch->Constraints[ConstraintNbr]->isInVirtualSpace;
}
bool isActive() const
{
assert(ConstraintNbr >= 0 && ConstraintNbr < sketch->Constraints.getSize());
return sketch->Constraints[ConstraintNbr]->isActive;
}
void updateVirtualSpaceStatus()
{
this->setCheckState((this->isInVirtualSpace() != sketchView->getIsShownVirtualSpace())
? Qt::Unchecked
: Qt::Checked);
}
const Sketcher::SketchObject* sketch;
const ViewProviderSketch* sketchView;
int ConstraintNbr;
QVariant value;
};
class ExpressionDelegate: public QStyledItemDelegate
{
public:
explicit ExpressionDelegate(QListWidget* _view)
: QStyledItemDelegate(_view)
, view(_view)
{}
protected:
QPixmap getIcon(const char* name, const QSize& size) const
{
QString key = QStringLiteral("%1_%2x%3")
.arg(QString::fromLatin1(name))
.arg(size.width())
.arg(size.height());
QPixmap icon;
if (QPixmapCache::find(key, &icon))
return icon;
icon = Gui::BitmapFactory().pixmapFromSvg(name, size);
if (!icon.isNull())
QPixmapCache::insert(key, icon);
return icon;
}
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const override
{
QStyleOptionViewItem options = option;
initStyleOption(&options, index);
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, option.widget);
ConstraintItem* item = dynamic_cast<ConstraintItem*>(view->item(index.row()));
if (!item || item->sketch->Constraints.getSize() <= item->ConstraintNbr)
return;
App::ObjectIdentifier path = item->sketch->Constraints.createPath(item->ConstraintNbr);
App::PropertyExpressionEngine::ExpressionInfo expr_info = item->sketch->getExpression(path);
// in case the constraint property is invalidated it returns a null pointer
const Sketcher::Constraint* constraint = item->sketch->Constraints[item->ConstraintNbr];
if (constraint && constraint->isDriving && expr_info.expression) {
// Paint pixmap
int s = 2 * options.rect.height() / 4;
int margin = s;
QPixmap pixmap = getIcon(":/icons/bound-expression.svg", QSize(s, s));
QRect r(options.rect);
r.setTop(r.top() + (r.height() - s) / 2);
r.setLeft(r.right() - s);
r.setHeight(s);
r.moveLeft(r.left() - margin);
painter->drawPixmap(r, pixmap);
}
}
QListWidget* view;
};
/* ConstraintView list widget ------------------------------*/
ConstraintView::ConstraintView(QWidget* parent)
: QListWidget(parent)
{
ExpressionDelegate* delegate = new ExpressionDelegate(this);
setItemDelegate(delegate);
}
ConstraintView::~ConstraintView()
{}
void ConstraintView::contextMenuEvent(QContextMenuEvent* event)
{
QMenu menu;
QListWidgetItem* item = currentItem();
QList<QListWidgetItem*> items = selectedItems();
// Cancel any in-progress operation
Gui::Document* doc = Gui::Application::Instance->activeDocument();
bool didRelease = SketcherGui::ReleaseHandler(doc);
// Sync the FreeCAD selection with the selection in the ConstraintView widget
if (didRelease && item) {
Gui::Selection().clearSelection();
std::string doc_name = static_cast<ConstraintItem*>(item)
->sketchView->getSketchObject()
->getDocument()
->getName();
std::string obj_name =
static_cast<ConstraintItem*>(item)->sketchView->getSketchObject()->getNameInDocument();
std::vector<std::string> constraintSubNames;
for (auto&& it : items) {
auto ci = static_cast<ConstraintItem*>(it);
std::string constraint_name =
Sketcher::PropertyConstraintList::getConstraintName(ci->ConstraintNbr);
constraintSubNames.emplace_back(constraint_name.c_str());
}
if (!constraintSubNames.empty())
Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames);
}
bool isQuantity = false;
bool isToggleDriving = false;
bool isActive = true;
// Non-driving-constraints/measurements
ConstraintItem* it = dynamic_cast<ConstraintItem*>(item);
if (it) {
// if its the right constraint
if (it->isDimensional()) {
isQuantity = true;
if (it->isEnforceable())
isToggleDriving = true;
}
isActive = it->isActive();
}
// This does the same as a double-click and thus it should be the first action and with bold
// text
QAction* change = menu.addAction(tr("Change Value"), this, &ConstraintView::modifyCurrentItem);
change->setEnabled(isQuantity);
menu.setDefaultAction(change);
QAction* driven =
menu.addAction(tr("Toggle Driving/Reference"), this, &ConstraintView::updateDrivingStatus);
driven->setEnabled(isToggleDriving);
QAction* activate = menu.addAction(
isActive ? tr("Deactivate") : tr("Activate"), this, &ConstraintView::updateActiveStatus);
activate->setEnabled(!items.isEmpty());
menu.addSeparator();
QAction* show = menu.addAction(tr("Show Constraints"), this, &ConstraintView::showConstraints);
show->setEnabled(!items.isEmpty());
QAction* hide = menu.addAction(tr("Hide Constraints"), this, &ConstraintView::hideConstraints);
hide->setEnabled(!items.isEmpty());
menu.addSeparator();
CONTEXT_ITEM("Sketcher_SelectElementsAssociatedWithConstraints",
"Select Elements",
"Sketcher_SelectElementsAssociatedWithConstraints",
doSelectConstraints,
true)
QAction* rename = menu.addAction(tr("Rename"), this, &ConstraintView::renameCurrentItem);
#ifndef Q_OS_MAC// on Mac F2 doesn't seem to trigger an edit signal
rename->setShortcut(QKeySequence(Qt::Key_F2));
#endif
rename->setEnabled(item != nullptr);
QAction* center =
menu.addAction(tr("Center Sketch"), this, &ConstraintView::centerSelectedItems);
center->setEnabled(item != nullptr);
QAction* remove = menu.addAction(tr("Delete"), this, &ConstraintView::deleteSelectedItems);
remove->setShortcut(QKeySequence(QKeySequence::Delete));
remove->setEnabled(!items.isEmpty());
QAction* swap = menu.addAction(
tr("Swap Constraint Names"), this, &ConstraintView::swapNamedOfSelectedItems);
swap->setEnabled(items.size() == 2);
menu.exec(event->globalPos());
}
CONTEXT_MEMBER_DEF("Sketcher_SelectElementsAssociatedWithConstraints", doSelectConstraints)
void ConstraintView::updateDrivingStatus()
{
QListWidgetItem* item = currentItem();
ConstraintItem* it = dynamic_cast<ConstraintItem*>(item);
if (it) {
Q_EMIT onUpdateDrivingStatus(item, !it->isDriving());
}
}
void ConstraintView::updateActiveStatus()
{
QListWidgetItem* item = currentItem();
ConstraintItem* it = dynamic_cast<ConstraintItem*>(item);
if (it) {
Q_EMIT onUpdateActiveStatus(item, !it->isActive());
}
}
void ConstraintView::showConstraints()
{
Q_EMIT emitShowSelection3DVisibility();
}
void ConstraintView::hideConstraints()
{
Q_EMIT emitHideSelection3DVisibility();
}
void ConstraintView::modifyCurrentItem()
{
Q_EMIT itemActivated(currentItem());
}
void ConstraintView::renameCurrentItem()
{
// See also TaskSketcherConstraints::on_listWidgetConstraints_itemChanged
QListWidgetItem* item = currentItem();
if (item)
editItem(item);
}
void ConstraintView::centerSelectedItems()
{
Q_EMIT emitCenterSelectedItems();
}
void ConstraintView::deleteSelectedItems()
{
App::Document* doc = App::GetApplication().getActiveDocument();
if (!doc)
return;
doc->openTransaction("Delete constraint");
std::vector<Gui::SelectionObject> sel = Gui::Selection().getSelectionEx(doc->getName());
for (std::vector<Gui::SelectionObject>::iterator ft = sel.begin(); ft != sel.end(); ++ft) {
Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(ft->getObject());
if (vp) {
vp->onDelete(ft->getSubNames());
}
}
doc->commitTransaction();
}
void ConstraintView::swapNamedOfSelectedItems()
{
QList<QListWidgetItem*> items = selectedItems();
if (items.size() != 2)
return;
ConstraintItem* item1 = static_cast<ConstraintItem*>(items[0]);
std::string escapedstr1 = Base::Tools::escapedUnicodeFromUtf8(
item1->sketch->Constraints[item1->ConstraintNbr]->Name.c_str());
ConstraintItem* item2 = static_cast<ConstraintItem*>(items[1]);
std::string escapedstr2 = Base::Tools::escapedUnicodeFromUtf8(
item2->sketch->Constraints[item2->ConstraintNbr]->Name.c_str());
// In commit 67800ec8c (21 Jul 2015) the implementation of
// on_listWidgetConstraints_itemChanged() has changed ensuring that a name of a constraint
// cannot be reset any more. This leads to some inconsistencies when trying to swap "empty"
// names.
//
// If names are empty then nothing should be done
if (escapedstr1.empty() || escapedstr2.empty()) {
Gui::TranslatedUserWarning(item1->sketch,
tr("Unnamed constraint"),
tr("Only the names of named constraints can be swapped."));
return;
}
std::stringstream ss;
ss << "DummyConstraint" << rand();
std::string tmpname = ss.str();
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Swap constraint names"));
Gui::cmdAppObjectArgs(
item1->sketch, "renameConstraint(%d, u'%s')", item1->ConstraintNbr, tmpname.c_str());
Gui::cmdAppObjectArgs(
item2->sketch, "renameConstraint(%d, u'%s')", item2->ConstraintNbr, escapedstr1.c_str());
Gui::cmdAppObjectArgs(
item1->sketch, "renameConstraint(%d, u'%s')", item1->ConstraintNbr, escapedstr2.c_str());
Gui::Command::commitCommand();
}
/* Filter constraints list widget ----------------------*/
ConstraintFilterList::ConstraintFilterList(QWidget* parent)
: QListWidget(parent)
{
normalFilterCount = filterItems.size() - 2;// All filter but selected and associated
selectedFilterIndex = normalFilterCount;
associatedFilterIndex = normalFilterCount + 1;
// default filters are all except for special filters
int defaultFilter = 0;
for (int i = 0; i < normalFilterCount; i++) {
defaultFilter |= 1 << i;
}
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher/General");
int filterState = hGrp->GetInt("SelectedConstraintFilters", defaultFilter);
for (auto const& filterItem : filterItems) {
Q_UNUSED(filterItem);
auto it = new QListWidgetItem();
it->setFlags(it->flags() | Qt::ItemIsUserCheckable);
addItem(it);
bool isChecked = static_cast<bool>(filterState & 1);// get the first bit of filterState
it->setCheckState(isChecked ? Qt::Checked : Qt::Unchecked);
filterState = filterState >> 1;// shift right to get rid of the used bit.
}
languageChange();
setPartiallyChecked();
}
ConstraintFilterList::~ConstraintFilterList()
{}
void ConstraintFilterList::changeEvent(QEvent* e)
{
if (e->type() == QEvent::LanguageChange)
languageChange();
QWidget::changeEvent(e);
}
void ConstraintFilterList::languageChange()
{
assert(static_cast<int>(filterItems.size()) == count());
int i = 0;
for (auto const& filterItem : filterItems) {
auto text = QStringLiteral(" ").repeated(filterItem.second - 1)
+ (filterItem.second > 0 ? QStringLiteral("- ") : QStringLiteral(""))
+ tr(filterItem.first);
item(i++)->setText(text);
}
}
void ConstraintFilterList::setPartiallyChecked()
{
/* If a group is partially checked or unchecked then we apply Qt::PartiallyChecked.
The for-loop index is starting from the end. This way sub-groups are first set, which enables
the bigger group to be set correctly after. Example: If we go from 0 to count, then the loop
starts at 'All' group, which check state of 'Geometric' which is not updated yet.*/
for (int i = normalFilterCount - 1; i >= 0; i--) {
bool mustBeChecked = true;
bool mustBeUnchecked = true;
int numberOfFilterInGroup = 0;
for (int j = 0; j < FilterValueLength; j++) {
if (i == j)
continue;
if (filterAggregates[i][j]) {// if it is in group
numberOfFilterInGroup++;
mustBeChecked = mustBeChecked && item(j)->checkState() == Qt::Checked;
mustBeUnchecked = mustBeUnchecked && item(j)->checkState() == Qt::Unchecked;
}
}
if (numberOfFilterInGroup > 1) {// avoid groups of single filters.
if (mustBeChecked)
item(i)->setCheckState(Qt::Checked);
else if (mustBeUnchecked)
item(i)->setCheckState(Qt::Unchecked);
else
item(i)->setCheckState(Qt::PartiallyChecked);
}
}
}
FilterValueBitset ConstraintFilterList::getMultiFilter()
{
FilterValueBitset tmpBitset;
for (int i = 0; i < normalFilterCount; i++) {
QListWidgetItem* it = item(i);
if (it->checkState() == Qt::Checked)
tmpBitset.set(i);
}
return tmpBitset;
}
// ----------------------------------------------------------------------------
TaskSketcherConstraints::TaskSketcherConstraints(ViewProviderSketch* sketchView)
: TaskBox(Gui::BitmapFactory().pixmap("Sketcher_CreateLineAngleLength"), tr("Constraints"), true, nullptr)
, specialFilterMode{SpecialFilterType::None}
, sketchView(sketchView)
, inEditMode(false)
, ui(new Ui_TaskSketcherConstraints)
{
// we need a separate container widget to add all controls to
proxy = new QWidget(this);
ui->setupUi(proxy);
ui->listWidgetConstraints->setSelectionMode(QAbstractItemView::ExtendedSelection);
ui->listWidgetConstraints->setEditTriggers(QListWidget::EditKeyPressed);
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher/General");
ui->filterBox->setChecked(hGrp->GetBool("ConstraintFilterEnabled", true));
ui->filterButton->setEnabled(ui->filterBox->isChecked());
// Create filter button
QWidgetAction* action = new QWidgetAction(this);
filterList = new ConstraintFilterList(this);
action->setDefaultWidget(filterList);
std::as_const(ui->filterButton)->addAction(action);
// Create local settings menu
// FIXME there is probably a smarter way to handle this menu
// FIXME translations aren't updated automatically at language change
QAction* action1 = new QAction(tr("Auto constraints"), this);
QAction* action2 = new QAction(tr("Auto remove redundant constraints"), this);
QAction* action3 = new QAction(tr("Display only filtered constraints"), this);
QAction* action4 = new QAction(tr("Extended information (in widget)"), this);
QAction* action5 = new QAction(tr("Hide internal alignment (in widget)"), this);
action1->setCheckable(true);
action2->setCheckable(true);
action3->setCheckable(true);
action4->setCheckable(true);
action5->setCheckable(true);
hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
{
QSignalBlocker block(this);
action1->setChecked(sketchView->Autoconstraints.getValue());
action2->setChecked(hGrp->GetBool("AutoRemoveRedundants", false));
action3->setChecked(hGrp->GetBool("VisualisationTrackingFilter", false));
action4->setChecked(hGrp->GetBool("ExtendedConstraintInformation", false));
action5->setChecked(hGrp->GetBool("HideInternalAlignment", false));
}
hGrp->Attach(this);
auto settingsBut = std::as_const(ui->settingsButton);
settingsBut->addAction(action1);
settingsBut->addAction(action2);
settingsBut->addAction(action3);
settingsBut->addAction(action4);
settingsBut->addAction(action5);
// connect needed signals
QObject::connect(ui->listWidgetConstraints,
&ConstraintView::itemSelectionChanged,
this,
&TaskSketcherConstraints::onListWidgetConstraintsItemSelectionChanged);
QObject::connect(ui->listWidgetConstraints,
&ConstraintView::itemActivated,
this,
&TaskSketcherConstraints::onListWidgetConstraintsItemActivated);
QObject::connect(ui->listWidgetConstraints,
&ConstraintView::itemChanged,
this,
&TaskSketcherConstraints::onListWidgetConstraintsItemChanged);
QObject::connect(ui->listWidgetConstraints,
&ConstraintView::emitCenterSelectedItems,
this,
&TaskSketcherConstraints::onListWidgetConstraintsEmitCenterSelectedItems);
QObject::connect(ui->listWidgetConstraints,
&ConstraintView::onUpdateDrivingStatus,
this,
&TaskSketcherConstraints::onListWidgetConstraintsUpdateDrivingStatus);
QObject::connect(ui->listWidgetConstraints,
&ConstraintView::onUpdateActiveStatus,
this,
&TaskSketcherConstraints::onListWidgetConstraintsUpdateActiveStatus);
QObject::connect(
ui->listWidgetConstraints,
&ConstraintView::emitHideSelection3DVisibility,
this,
&TaskSketcherConstraints::onListWidgetConstraintsEmitHideSelection3DVisibility);
QObject::connect(
ui->listWidgetConstraints,
&ConstraintView::emitShowSelection3DVisibility,
this,
&TaskSketcherConstraints::onListWidgetConstraintsEmitShowSelection3DVisibility);
#if QT_VERSION >= QT_VERSION_CHECK(6,7,0)
QObject::connect(ui->filterBox,
&QCheckBox::checkStateChanged,
this,
&TaskSketcherConstraints::onFilterBoxStateChanged);
#else
QObject::connect(ui->filterBox,
&QCheckBox::stateChanged,
this,
&TaskSketcherConstraints::onFilterBoxStateChanged);
#endif
QObject::connect(
ui->filterButton, &QToolButton::clicked, ui->filterButton, &QToolButton::showMenu);
QObject::connect(ui->showHideButton,
&QToolButton::clicked,
this,
&TaskSketcherConstraints::onShowHideButtonClicked);
QObject::connect(
ui->settingsButton, &QToolButton::clicked, ui->settingsButton, &QToolButton::showMenu);
QObject::connect(action1,
&QAction::triggered,// 'triggered' is emitted only on user action. This is
// defensive. See if 'toggled' is needed
this,
&TaskSketcherConstraints::onSettingsAutoConstraintsChanged);
QObject::connect(action2,
&QAction::triggered,
this,
&TaskSketcherConstraints::onSettingsAutoRemoveRedundantChanged);
QObject::connect(action3,
&QAction::triggered,
this,
&TaskSketcherConstraints::onSettingsRestrictVisibilityChanged);
QObject::connect(action4,
&QAction::triggered,
this,
&TaskSketcherConstraints::onSettingsExtendedInformationChanged);
QObject::connect(action5,
&QAction::triggered,
this,
&TaskSketcherConstraints::onSettingsHideInternalAligmentChanged);
QObject::connect(filterList,
&ConstraintFilterList::itemChanged,
this,
&TaskSketcherConstraints::onFilterListItemChanged);
//NOLINTBEGIN
connectionConstraintsChanged = sketchView->signalConstraintsChanged.connect(
std::bind(&SketcherGui::TaskSketcherConstraints::slotConstraintsChanged, this));
//NOLINTEND
this->groupLayout()->addWidget(proxy);
// Initialize special filters
for (int i = filterList->normalFilterCount; i < filterList->count(); i++) {
if (filterList->item(i)->checkState() == Qt::Checked) {
onFilterListItemChanged(filterList->item(i));
}
}
multiFilterStatus = filterList->getMultiFilter();
ui->listWidgetConstraints->setStyleSheet(QStringLiteral("margin-top: 0px"));
//NOLINTBEGIN
Gui::Application* app = Gui::Application::Instance;
changedSketchView = app->signalChangedObject.connect(
std::bind(&TaskSketcherConstraints::onChangedSketchView, this, sp::_1, sp::_2));
//NOLINTEND
updateList();
}
TaskSketcherConstraints::~TaskSketcherConstraints()
{
connectionConstraintsChanged.disconnect();
App::GetApplication()
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher")
->Detach(this);
}
void TaskSketcherConstraints::onSettingsExtendedInformationChanged(bool value)
{
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
if (hGrp->GetBool("ExtendedConstraintInformation", false) != value) {
hGrp->SetBool("ExtendedConstraintInformation", value);
}
slotConstraintsChanged();
}
void TaskSketcherConstraints::onSettingsHideInternalAligmentChanged(bool value)
{
// synchronise parameter
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
if (hGrp->GetBool("HideInternalAlignment", false) != value) {
hGrp->SetBool("HideInternalAlignment", value);
}
slotConstraintsChanged();
}
void TaskSketcherConstraints::onSettingsRestrictVisibilityChanged(bool value)
{
// synchronise VisualisationTrackingFilter parameter
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
if (hGrp->GetBool("VisualisationTrackingFilter", false) != value) {
hGrp->SetBool("VisualisationTrackingFilter", value);
}
change3DViewVisibilityToTrackFilter(value);
}
void TaskSketcherConstraints::onSettingsAutoConstraintsChanged(bool value)
{
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
Base::ConnectionBlocker block(changedSketchView);
sketchView->Autoconstraints.setValue(value);
}
void TaskSketcherConstraints::onSettingsAutoRemoveRedundantChanged(bool value)
{
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
if (hGrp->GetBool("AutoRemoveRedundants", false) != value) {
hGrp->SetBool("AutoRemoveRedundants", value);
}
}
void TaskSketcherConstraints::onChangedSketchView(const Gui::ViewProvider& vp,
const App::Property& prop)
{
if (sketchView == &vp) {
if (&sketchView->Autoconstraints == &prop) {
QSignalBlocker block(std::as_const(ui->settingsButton)->actions()[0]);
std::as_const(ui->settingsButton)
->actions()[0]
->setChecked(sketchView->Autoconstraints.getValue());
}
}
}
void TaskSketcherConstraints::onFilterBoxStateChanged(int val)
{
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher/General");
hGrp->SetBool("ConstraintFilterEnabled", val == Qt::Checked);
ui->filterButton->setEnabled(val == Qt::Checked);
updateList();
}
/*hide all show all button =====================================================*/
void TaskSketcherConstraints::onShowHideButtonClicked(bool val)
{
Q_UNUSED(val)
bool allSelected = true;
for (int i = 0; i < ui->listWidgetConstraints->count(); ++i) {
QListWidgetItem* it = ui->listWidgetConstraints->item(i);
if (!(it->isHidden()) && it->checkState() == Qt::Unchecked) {
allSelected = false;
break;
}
}
changeFilteredVisibility(!allSelected);
}
/* Right click functionalities for constraint list view =========================*/
void TaskSketcherConstraints::onListWidgetConstraintsEmitHideSelection3DVisibility()
{
changeFilteredVisibility(false, ActionTarget::Selected);
}
void TaskSketcherConstraints::onListWidgetConstraintsEmitShowSelection3DVisibility()
{
changeFilteredVisibility(true, ActionTarget::Selected);
}
void TaskSketcherConstraints::changeFilteredVisibility(bool show, ActionTarget target)
{
assert(sketchView);
auto selecteditems = ui->listWidgetConstraints->selectedItems();
std::vector<int> constrIds;
for (int i = 0; i < ui->listWidgetConstraints->count(); ++i) {
QListWidgetItem* item = ui->listWidgetConstraints->item(i);
bool processItem = false;
if (target == ActionTarget::All) {
processItem = !item->isHidden();
}
else if (target == ActionTarget::Selected) {
if (selecteditems.contains(item))
processItem = true;
}
if (processItem) {// The item is shown in the filtered list
const ConstraintItem* it = dynamic_cast<const ConstraintItem*>(item);
if (!it)
continue;
// must change state is shown and is to be hidden or hidden and must change state is
// shown
if ((it->isInVirtualSpace() == sketchView->getIsShownVirtualSpace() && !show)
|| (it->isInVirtualSpace() != sketchView->getIsShownVirtualSpace() && show)) {
constrIds.push_back(it->ConstraintNbr);
}
}
}
if (!constrIds.empty()) {
doSetVirtualSpace(constrIds, !show);
}
}
void TaskSketcherConstraints::onListWidgetConstraintsUpdateDrivingStatus(QListWidgetItem* item,
bool status)
{
Q_UNUSED(status);
ConstraintItem* citem = dynamic_cast<ConstraintItem*>(item);
if (!citem)
return;
Gui::Application::Instance->commandManager().runCommandByName(
"Sketcher_ToggleDrivingConstraint");
slotConstraintsChanged();
}
void TaskSketcherConstraints::onListWidgetConstraintsUpdateActiveStatus(QListWidgetItem* item,
bool status)
{
Q_UNUSED(status);
ConstraintItem* citem = dynamic_cast<ConstraintItem*>(item);
if (!citem)
return;
Gui::Application::Instance->commandManager().runCommandByName(
"Sketcher_ToggleActiveConstraint");
slotConstraintsChanged();
}
void TaskSketcherConstraints::onListWidgetConstraintsItemActivated(QListWidgetItem* item)
{
ConstraintItem* it = dynamic_cast<ConstraintItem*>(item);
if (!it)
return;
// if its the right constraint
if (it->isDimensional()) {
EditDatumDialog* editDatumDialog = new EditDatumDialog(this->sketchView, it->ConstraintNbr);
editDatumDialog->exec(false);
delete editDatumDialog;
}
}
void TaskSketcherConstraints::onListWidgetConstraintsItemChanged(QListWidgetItem* item)
{
const ConstraintItem* it = dynamic_cast<const ConstraintItem*>(item);
if (!it || inEditMode)
return;
inEditMode = true;
assert(sketchView);
const Sketcher::SketchObject* sketch = sketchView->getSketchObject();
const std::vector<Sketcher::Constraint*>& vals = sketch->Constraints.getValues();
const Sketcher::Constraint* v = vals[it->ConstraintNbr];
const std::string currConstraintName = v->Name;
const std::string basename = it->data(Qt::EditRole).toString().toStdString();
std::string newName(
Sketcher::PropertyConstraintList::getConstraintName(basename, it->ConstraintNbr));
// we only start a rename if we are really sure the name has changed, which is:
// a) that the name generated by the constraints is different from the text in the widget item
// b) that the text in the widget item, basename, is not ""
// otherwise a checkbox change will trigger a rename on the first execution, bloating the
// constraint icons with the default constraint name "constraint1, constraint2"
if (newName != currConstraintName && !basename.empty()) {
if (!SketcherGui::checkConstraintName(sketch, newName)) {
newName = currConstraintName;
}
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Rename sketch constraint"));
try {
Gui::cmdAppObjectArgs(
sketch, "renameConstraint(%d, u'%s')", it->ConstraintNbr, newName.c_str());
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Gui::Command::abortCommand();
Gui::NotifyUserError(
sketch, QT_TRANSLATE_NOOP("Notifications", "Value Error"), e.what());
}
}
std::vector<int> constraintNum = {it->ConstraintNbr};
doSetVirtualSpace(constraintNum, (item->checkState() == Qt::Checked) == sketchView->getIsShownVirtualSpace());
inEditMode = false;
}
void TaskSketcherConstraints::updateSelectionFilter()
{
// Snapshot current selection
auto items = ui->listWidgetConstraints->selectedItems();
selectionFilter.clear();
for (const auto& item : items)
selectionFilter.push_back(static_cast<ConstraintItem*>(item)->ConstraintNbr);
}
void TaskSketcherConstraints::updateAssociatedConstraintsFilter()
{
associatedConstraintsFilter.clear();
assert(sketchView);
std::vector<Gui::SelectionObject> selection;
selection = Gui::Selection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId());
// only one sketch with its subelements are allowed to be selected
if (selection.size() != 1) {
return;
}
// get the needed lists and objects
const std::vector<std::string>& SubNames = selection[0].getSubNames();
const Sketcher::SketchObject* Obj = sketchView->getSketchObject();
const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
std::vector<std::string> constraintSubNames;
// go through the selected subelements
for (std::vector<std::string>::const_iterator it = SubNames.begin(); it != SubNames.end();
++it) {
// only handle edges
if (it->size() > 4 && it->substr(0, 4) == "Edge") {
int GeoId = std::atoi(it->substr(4, 4000).c_str()) - 1;
// push all the constraints
int i = 0;
for (std::vector<Sketcher::Constraint*>::const_iterator it = vals.begin();
it != vals.end();
++it, ++i) {
if ((*it)->First == GeoId || (*it)->Second == GeoId || (*it)->Third == GeoId) {
associatedConstraintsFilter.push_back(i);
}
}
}
}
}
void TaskSketcherConstraints::updateList()
{
multiFilterStatus =
filterList->getMultiFilter();// moved here in case the filter is changed programmatically.
// new constraints have to be added first
slotConstraintsChanged();
// enforce constraint visibility
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
bool visibilityTracksFilter = hGrp->GetBool("VisualisationTrackingFilter", false);
change3DViewVisibilityToTrackFilter(visibilityTracksFilter);
}
void TaskSketcherConstraints::onSelectionChanged(const Gui::SelectionChanges& msg)
{
assert(sketchView);
std::string temp;
if (msg.Type == Gui::SelectionChanges::ClrSelection) {
auto tmpBlock = ui->listWidgetConstraints->blockSignals(true);
ui->listWidgetConstraints->clearSelection();
ui->listWidgetConstraints->blockSignals(tmpBlock);
if (specialFilterMode == SpecialFilterType::Selected) {
updateSelectionFilter();
bool block = this->blockSelection(true);// avoid to be notified by itself
updateList();
this->blockSelection(block);
}
else if (specialFilterMode == SpecialFilterType::Associated) {
associatedConstraintsFilter.clear();
updateList();
}
}
else if (msg.Type == Gui::SelectionChanges::AddSelection
|| msg.Type == Gui::SelectionChanges::RmvSelection) {
bool select = (msg.Type == Gui::SelectionChanges::AddSelection);
// is it this object??
if (strcmp(msg.pDocName, sketchView->getSketchObject()->getDocument()->getName()) == 0
&& strcmp(msg.pObjectName, sketchView->getSketchObject()->getNameInDocument()) == 0) {
if (msg.pSubName) {
QRegularExpression rx(QStringLiteral("^Constraint(\\d+)$"));
QRegularExpressionMatch match;
QString expr = QString::fromLatin1(msg.pSubName);
boost::ignore_unused(expr.indexOf(rx, 0, &match));
if (match.hasMatch()) {// is a constraint
bool ok;
int ConstrId = match.captured(1).toInt(&ok) - 1;
if (ok) {
int countItems = ui->listWidgetConstraints->count();
for (int i = 0; i < countItems; i++) {
ConstraintItem* item =
static_cast<ConstraintItem*>(ui->listWidgetConstraints->item(i));
if (item->ConstraintNbr == ConstrId) {
auto tmpBlock = ui->listWidgetConstraints->blockSignals(true);
item->setSelected(select);
ui->listWidgetConstraints->blockSignals(tmpBlock);
SketcherGui::scrollTo(ui->listWidgetConstraints, i, select);
break;
}
}
if (specialFilterMode == SpecialFilterType::Selected) {
updateSelectionFilter();
bool block =
this->blockSelection(true);// avoid to be notified by itself
updateList();
this->blockSelection(block);
}
}
}
else if (specialFilterMode == SpecialFilterType::Associated) {// is NOT a constraint
int geoid = Sketcher::GeoEnum::GeoUndef;
Sketcher::PointPos pointpos = Sketcher::PointPos::none;
getSelectionGeoId(expr, geoid, pointpos);
if (geoid != Sketcher::GeoEnum::GeoUndef
&& pointpos == Sketcher::PointPos::none) {
// It is not possible to update on single addition/removal of a geometric
// element, as one removal may imply removing a constraint that should be
// added by a different element that is still selected. The necessary checks
// outweigh a full rebuild of the filter.
updateAssociatedConstraintsFilter();
updateList();
}
}
}
}
}
else if (msg.Type == Gui::SelectionChanges::SetSelection) {
// do nothing here
}
}
void TaskSketcherConstraints::OnChange(Base::Subject<const char*>& rCaller, const char* rcReason)
{
Q_UNUSED(rCaller);
int actNum = -1;
auto hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
if (strcmp(rcReason, "AutoRemoveRedundants") == 0) {
actNum = 1;
}
else if (strcmp(rcReason, "VisualisationTrackingFilter") == 0) {
actNum = 2;
}
else if (strcmp(rcReason, "ExtendedConstraintInformation") == 0) {
actNum = 3;
}
else if (strcmp(rcReason, "HideInternalAlignment") == 0) {
actNum = 4;
}
if (actNum >= 0) {
assert(actNum < static_cast<int>(ui->settingsButton->actions().size()));
std::as_const(ui->settingsButton)->actions()[actNum]->setChecked(hGrp->GetBool(rcReason, false));
}
}
void TaskSketcherConstraints::getSelectionGeoId(QString expr, int& geoid,
Sketcher::PointPos& pointpos)
{
QRegularExpression rxEdge(QStringLiteral("^Edge(\\d+)$"));
QRegularExpressionMatch match;
boost::ignore_unused(expr.indexOf(rxEdge, 0, &match));
geoid = Sketcher::GeoEnum::GeoUndef;
pointpos = Sketcher::PointPos::none;
if (match.hasMatch()) {
bool ok;
int edgeId = match.captured(1).toInt(&ok) - 1;
if (ok) {
geoid = edgeId;
}
}
else {
QRegularExpression rxVertex(QStringLiteral("^Vertex(\\d+)$"));
boost::ignore_unused(expr.indexOf(rxVertex, 0, &match));
if (match.hasMatch()) {
bool ok;
int vertexId = match.captured(1).toInt(&ok) - 1;
if (ok) {
const Sketcher::SketchObject* sketch = sketchView->getSketchObject();
sketch->getGeoVertexIndex(vertexId, geoid, pointpos);
}
}
}
}
void TaskSketcherConstraints::onListWidgetConstraintsEmitCenterSelectedItems()
{
sketchView->centerSelection();
}
void TaskSketcherConstraints::onListWidgetConstraintsItemSelectionChanged()
{
std::string doc_name = sketchView->getSketchObject()->getDocument()->getName();
std::string obj_name = sketchView->getSketchObject()->getNameInDocument();
bool block = this->blockSelection(true);// avoid to be notified by itself
Gui::Selection().clearSelection();
std::vector<std::string> constraintSubNames;
QList<QListWidgetItem*> items = ui->listWidgetConstraints->selectedItems();
for (QList<QListWidgetItem*>::iterator it = items.begin(); it != items.end(); ++it) {
std::string constraint_name(Sketcher::PropertyConstraintList::getConstraintName(
static_cast<ConstraintItem*>(*it)->ConstraintNbr));
constraintSubNames.push_back(constraint_name);
}
if (!constraintSubNames.empty())
Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames);
this->blockSelection(block);
// it seems that addSelections gives back the focus to the view, and not immediately.
QTimer::singleShot(200, [this]() {
ui->listWidgetConstraints->setFocus();
});
}
void TaskSketcherConstraints::change3DViewVisibilityToTrackFilter(bool filterEnabled)
{
assert(sketchView);
// Build up ListView with the constraints
const Sketcher::SketchObject* sketch = sketchView->getSketchObject();
const std::vector<Sketcher::Constraint*>& vals = sketch->Constraints.getValues();
std::vector<int> constrIdsToSetVisible;
std::vector<int> constrIdsToSetHidden;
for (std::size_t i = 0; i < vals.size(); ++i) {
ConstraintItem* it = static_cast<ConstraintItem*>(ui->listWidgetConstraints->item(i));
bool visible = !filterEnabled || !isConstraintFiltered(it);
// If the constraint is filteredout and it was previously shown in 3D view
if (!visible) {
constrIdsToSetHidden.push_back(it->ConstraintNbr);
}
else if (visible) {
constrIdsToSetVisible.push_back(it->ConstraintNbr);
}
}
if (!constrIdsToSetVisible.empty()) {
bool ret = doSetVisible(constrIdsToSetVisible, true);
if (!ret) {
return;
}
}
if (!constrIdsToSetHidden.empty()) {
bool ret = doSetVisible(constrIdsToSetHidden, false);
if (!ret) {
return;
}
}
if (constrIdsToSetVisible.empty() && constrIdsToSetHidden.empty()) {
slotConstraintsChanged();
}
}
bool TaskSketcherConstraints::doSetVirtualSpace(const std::vector<int>& constrIds, bool isvirtualspace) {
assert(sketchView);
const Sketcher::SketchObject* sketch = sketchView->getSketchObject();
std::stringstream stream;
stream << '[';
for (size_t i = 0; i < constrIds.size() - 1; i++) {
stream << constrIds[i] << ",";
}
stream << constrIds[constrIds.size() - 1] << ']';
std::string constrIdList = stream.str();
Gui::Command::openCommand(
QT_TRANSLATE_NOOP("Command", "Update constraint's virtual space"));
try {
Gui::cmdAppObjectArgs(sketch,
"setVirtualSpace(%s, %s)",
constrIdList,
isvirtualspace ? "True" : "False");
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Gui::Command::abortCommand();
Gui::TranslatedUserError(
sketch, tr("Error"), tr("Impossible to update visibility tracking:") + QLatin1String(" ") + QLatin1String(e.what()));
return false;
}
return true;
}
bool TaskSketcherConstraints::doSetVisible(const std::vector<int>& constrIds, bool isVisible) {
assert(sketchView);
const Sketcher::SketchObject* sketch = sketchView->getSketchObject();
std::stringstream stream;
stream << '[';
for (size_t i = 0; i < constrIds.size() - 1; i++) {
stream << constrIds[i] << ",";
}
stream << constrIds[constrIds.size() - 1] << ']';
std::string constrIdList = stream.str();
Gui::Command::openCommand(
QT_TRANSLATE_NOOP("Command", "Update constraint's visibility"));
try {
Gui::cmdAppObjectArgs(sketch,
"setVisibility(%s, %s)",
constrIdList,
isVisible ? "True" : "False");
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Gui::Command::abortCommand();
Gui::TranslatedUserError(
sketch, tr("Error"), tr("Impossible to update visibility:") + QLatin1String(" ") + QLatin1String(e.what()));
return false;
}
return true;
}
bool TaskSketcherConstraints::isConstraintFiltered(QListWidgetItem* item)
{
assert(sketchView);
const Sketcher::SketchObject* sketch = sketchView->getSketchObject();
const std::vector<Sketcher::Constraint*>& vals = sketch->Constraints.getValues();
ConstraintItem* it = static_cast<ConstraintItem*>(item);
const Sketcher::Constraint* constraint = vals[it->ConstraintNbr];
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
bool hideInternalAlignment = hGrp->GetBool("HideInternalAlignment", false);
bool visible = true;
if (ui->filterBox->checkState() == Qt::Checked) {
// First select only the filtered one.
switch (constraint->Type) {
case Sketcher::Horizontal:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Horizontal);
break;
case Sketcher::Vertical:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Vertical);
break;
case Sketcher::Coincident:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Coincident);
break;
case Sketcher::PointOnObject:
visible = checkFilterBitset(multiFilterStatus, FilterValue::PointOnObject);
break;
case Sketcher::Parallel:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Parallel);
break;
case Sketcher::Perpendicular:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Perpendicular);
break;
case Sketcher::Tangent:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Tangent);
break;
case Sketcher::Equal:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Equality);
break;
case Sketcher::Symmetric:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Symmetric);
break;
case Sketcher::Block:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Block);
break;
case Sketcher::Distance:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Distance);
break;
case Sketcher::DistanceX:
visible = checkFilterBitset(multiFilterStatus, FilterValue::HorizontalDistance);
break;
case Sketcher::DistanceY:
visible = checkFilterBitset(multiFilterStatus, FilterValue::VerticalDistance);
break;
case Sketcher::Radius:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Radius);
break;
case Sketcher::Weight:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Weight);
break;
case Sketcher::Diameter:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Diameter);
break;
case Sketcher::Angle:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Angle);
break;
case Sketcher::SnellsLaw:
visible = checkFilterBitset(multiFilterStatus, FilterValue::SnellsLaw);
break;
case Sketcher::InternalAlignment:
visible = checkFilterBitset(multiFilterStatus, FilterValue::InternalAlignment)
&& !hideInternalAlignment;
default:
break;
}
visible |= !constraint->Name.empty() && checkFilterBitset(multiFilterStatus, FilterValue::Named);
// Then we re-filter based on selected/associated if such mode selected.
if (visible && specialFilterMode == SpecialFilterType::Selected) {
visible = (std::ranges::find(selectionFilter, it->ConstraintNbr) != selectionFilter.end());
}
else if (visible && specialFilterMode == SpecialFilterType::Associated) {
visible = (std::ranges::find(associatedConstraintsFilter, it->ConstraintNbr)
!= associatedConstraintsFilter.end());
}
}
else if (constraint->Type == Sketcher::InternalAlignment) {
visible = !hideInternalAlignment;
}
return !visible;
}
void TaskSketcherConstraints::slotConstraintsChanged()
{
assert(sketchView);
// Build up ListView with the constraints
const Sketcher::SketchObject* sketch = sketchView->getSketchObject();
const std::vector<Sketcher::Constraint*>& vals = sketch->Constraints.getValues();
/* Update constraint number and virtual space check status */
for (int i = 0; i < ui->listWidgetConstraints->count(); ++i) {
ConstraintItem* it = dynamic_cast<ConstraintItem*>(ui->listWidgetConstraints->item(i));
assert(it);
it->ConstraintNbr = i;
it->value = QVariant();
}
/* Remove entries, if any */
for (std::size_t i = ui->listWidgetConstraints->count(); i > vals.size(); --i)
delete ui->listWidgetConstraints->takeItem(i - 1);
/* Add new entries, if any */
for (std::size_t i = ui->listWidgetConstraints->count(); i < vals.size(); ++i)
ui->listWidgetConstraints->addItem(new ConstraintItem(sketch, sketchView, i));
/* Update the states */
auto tmpBlock = ui->listWidgetConstraints->blockSignals(true);
for (int i = 0; i < ui->listWidgetConstraints->count(); ++i) {
ConstraintItem* it = static_cast<ConstraintItem*>(ui->listWidgetConstraints->item(i));
it->updateVirtualSpaceStatus();
}
ui->listWidgetConstraints->blockSignals(tmpBlock);
/* Update filtering */
for (std::size_t i = 0; i < vals.size(); ++i) {
const Sketcher::Constraint* constraint = vals[i];
ConstraintItem* it = static_cast<ConstraintItem*>(ui->listWidgetConstraints->item(i));
bool visible = !isConstraintFiltered(it);
// block signals as there is no need to invoke the
// on_listWidgetConstraints_itemChanged() slot in
// case a name has changed because this function gets
// called after changing the constraint list property
QAbstractItemModel* model = ui->listWidgetConstraints->model();
auto tmpBlock = model->blockSignals(true);
it->setHidden(!visible);
it->setData(Qt::EditRole, QString::fromStdString(constraint->Name));
model->blockSignals(tmpBlock);
}
}
void TaskSketcherConstraints::changeEvent(QEvent* e)
{
TaskBox::changeEvent(e);
if (e->type() == QEvent::LanguageChange) {
ui->retranslateUi(proxy);
}
}
/* list for multi filter */
void TaskSketcherConstraints::onFilterListItemChanged(QListWidgetItem* item)
{
int filterindex = filterList->row(item);
auto tmpBlock = filterList->blockSignals(true);
if (filterindex < filterList->normalFilterCount) {
auto itemAggregate = filterAggregates[filterindex];
/*First, if this is a group, we need to set the same state to all of its children.
ie any filter comprised on the filter of the activated item, gets the same check state.*/
for (int i = 0; i < filterList->normalFilterCount; i++) {
if (itemAggregate[i])
filterList->item(i)->setCheckState(item->checkState());
}
/* Now we also need to see if any modified group is all checked or all unchecked and set
* their status accordingly*/
filterList->setPartiallyChecked();
}
else if (filterindex == filterList->selectedFilterIndex) {// Selected constraints
if (item->checkState() == Qt::Checked) {
specialFilterMode = SpecialFilterType::Selected;
updateSelectionFilter();
}
else {
specialFilterMode = SpecialFilterType::None;
}
filterList->item(filterList->associatedFilterIndex)
->setCheckState(Qt::Unchecked); // Disable 'associated'
}
else {// Associated constraints
if (item->checkState() == Qt::Checked) {
specialFilterMode = SpecialFilterType::Associated;
updateAssociatedConstraintsFilter();
}
else {
specialFilterMode = SpecialFilterType::None;
}
filterList->item(filterList->selectedFilterIndex)
->setCheckState(Qt::Unchecked); // Disable 'selected'
}
filterList->blockSignals(tmpBlock);
// Save the state of the filter.
int filterState = 0;
for (int i = filterList->count() - 1; i >= 0; i--) {
bool isChecked = filterList->item(i)->checkState() == Qt::Checked;
filterState = filterState << 1;// we shift left first, else the list is shifted at the end.
filterState = filterState | (isChecked ? 1 : 0);
}
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher/General");
hGrp->SetInt("SelectedConstraintFilters", filterState);
// if tracking, it will call slotConstraintChanged via update mechanism as Multi Filter affects
// not only visibility, but also filtered list content, if not tracking will still update the
// list to match the multi-filter.
updateList();
}
#include "moc_TaskSketcherConstraints.cpp"
// clang-format on