This creates new method overload for changing layer of exact ElementItem. This fixes strange behavior of the checkboxes for elements that were dependent not on what user clicked but on what is selected. Fixes: #8814
1984 lines
83 KiB
C++
1984 lines
83 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2014 Abdullah Tahiri <abdullah.tahiri.yo@gmail.com> *
|
|
* *
|
|
* 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 "PreCompiled.h"
|
|
#ifndef _PreComp_
|
|
#include <QContextMenuEvent>
|
|
#include <QImage>
|
|
#include <QMenu>
|
|
#include <QPainter>
|
|
#include <QPixmap>
|
|
#include <QRegularExpression>
|
|
#include <QRegularExpressionMatch>
|
|
#include <QShortcut>
|
|
#include <QString>
|
|
#include <QWidgetAction>
|
|
#include <boost/core/ignore_unused.hpp>
|
|
#endif
|
|
|
|
#include <App/Application.h>
|
|
#include <App/Document.h>
|
|
#include <App/DocumentObject.h>
|
|
#include <Gui/Application.h>
|
|
#include <Gui/BitmapFactory.h>
|
|
#include <Gui/Command.h>
|
|
#include <Gui/Notifications.h>
|
|
#include <Gui/Selection.h>
|
|
#include <Gui/SelectionObject.h>
|
|
#include <Gui/ViewProvider.h>
|
|
#include <Mod/Sketcher/App/GeometryFacade.h>
|
|
#include <Mod/Sketcher/App/SketchObject.h>
|
|
|
|
#include "TaskSketcherElements.h"
|
|
#include "Utils.h"
|
|
#include "ViewProviderSketch.h"
|
|
#include "ui_TaskSketcherElements.h"
|
|
|
|
// clang-format off
|
|
using namespace SketcherGui;
|
|
using namespace Gui::TaskView;
|
|
|
|
// Translation block for context menu: do not remove
|
|
#if 0
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Point Coincidence");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Point on Object");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Vertical Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Horizontal Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Parallel Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Perpendicular Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Tangent Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Equal Length");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Symmetric");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Block Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Lock Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Horizontal Distance");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Vertical Distance");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Length Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Radius Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Diameter Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Radiam Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Angle Constraint");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Toggle construction geometry");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Select Constraints");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Select Origin");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Select Horizontal Axis");
|
|
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Select Vertical Axis");
|
|
#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 ElementView::FUNC() \
|
|
{ \
|
|
Gui::Application::Instance->commandManager().runCommandByName(CMDSTR); \
|
|
}
|
|
|
|
|
|
namespace SketcherGui
|
|
{
|
|
|
|
class ElementItemDelegate: public QStyledItemDelegate
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
/// Enum containing all controls rendered in this item. Controls in that enum MUST be in order.
|
|
enum SubControl : int {
|
|
CheckBox,
|
|
LineSelect,
|
|
StartSelect,
|
|
EndSelect,
|
|
MidSelect,
|
|
Label
|
|
};
|
|
|
|
explicit ElementItemDelegate(ElementView* parent);
|
|
~ElementItemDelegate() override = default;
|
|
|
|
void paint(QPainter* painter, const QStyleOptionViewItem& option,
|
|
const QModelIndex& index) const override;
|
|
bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option,
|
|
const QModelIndex& index) override;
|
|
|
|
ElementItem* getElementItem(const QModelIndex& index) const;
|
|
|
|
QRect subControlRect(SubControl element, const QStyleOptionViewItem& option, const QModelIndex& index) const;
|
|
void drawSubControl(SubControl element, QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
|
|
|
|
const int gap = 4; // 4px of spacing between consecutive elements
|
|
|
|
Q_SIGNALS:
|
|
void itemHovered(QModelIndex);
|
|
void itemChecked(QModelIndex, Qt::CheckState state);
|
|
};
|
|
|
|
// clang-format on
|
|
// helper class to store additional information about the listWidget entry.
|
|
class ElementItem: public QListWidgetItem
|
|
{
|
|
public:
|
|
enum class GeometryState
|
|
{
|
|
Normal,
|
|
Construction,
|
|
InternalAlignment,
|
|
External
|
|
};
|
|
|
|
enum class Layer
|
|
{
|
|
Default = 0,
|
|
Discontinuous = 1,
|
|
Hidden = 2,
|
|
};
|
|
|
|
ElementItem(int elementnr,
|
|
int startingVertex,
|
|
int midVertex,
|
|
int endVertex,
|
|
Base::Type geometryType,
|
|
GeometryState state,
|
|
const QString& lab,
|
|
ViewProviderSketch* sketchView)
|
|
: ElementNbr(elementnr)
|
|
, StartingVertex(startingVertex)
|
|
, MidVertex(midVertex)
|
|
, EndVertex(endVertex)
|
|
, GeometryType(std::move(geometryType))
|
|
, State(state)
|
|
, isLineSelected(false)
|
|
, isStartingPointSelected(false)
|
|
, isEndPointSelected(false)
|
|
, isMidPointSelected(false)
|
|
, clickedOn(SubElementType::none)
|
|
, hovered(SubElementType::none)
|
|
, rightClicked(false)
|
|
, label(lab)
|
|
, sketchView(sketchView)
|
|
{}
|
|
|
|
~ElementItem() override
|
|
{}
|
|
|
|
bool isVisible()
|
|
{
|
|
if (State != GeometryState::External) {
|
|
const auto geo = sketchView->getSketchObject()->getGeometry(ElementNbr);
|
|
if (geo) {
|
|
auto layer = getSafeGeomLayerId(geo);
|
|
|
|
return layer != static_cast<unsigned int>(Layer::Hidden);
|
|
}
|
|
}
|
|
|
|
// 1. external geometry currently is always visible.
|
|
// 2. if internal and ElementNbr is out of range, the element
|
|
// needs to be updated and the return value is not important.
|
|
return true;
|
|
}
|
|
|
|
QVariant data(int role) const override
|
|
{
|
|
// In order for content-box to include size of the 4 geometry icons we need to provide
|
|
// Qt with information about decoration (icon) size. This is hack to work around Qt
|
|
// limitation of not knowing about padding, border and margin boxes of stylesheets
|
|
// thus being unable to provide proper sizeHint for stylesheets to render correctly
|
|
if (role == Qt::DecorationRole) {
|
|
int size = listWidget()->style()->pixelMetric(QStyle::PM_ListViewIconSize);
|
|
|
|
return QIcon(QPixmap(QSize(size, size)));
|
|
}
|
|
|
|
return QListWidgetItem::data(role);
|
|
}
|
|
|
|
bool isGeometrySelected(Sketcher::PointPos pos) const
|
|
{
|
|
switch (pos) {
|
|
case Sketcher::PointPos::none:
|
|
return isLineSelected;
|
|
case Sketcher::PointPos::start:
|
|
return isStartingPointSelected;
|
|
case Sketcher::PointPos::end:
|
|
return isEndPointSelected;
|
|
case Sketcher::PointPos::mid:
|
|
return isMidPointSelected;
|
|
}
|
|
}
|
|
|
|
Sketcher::SketchObject* getSketchObject() const
|
|
{
|
|
return sketchView->getSketchObject();
|
|
}
|
|
|
|
int ElementNbr;
|
|
int StartingVertex;
|
|
int MidVertex;
|
|
int EndVertex;
|
|
|
|
Base::Type GeometryType;
|
|
GeometryState State;
|
|
|
|
bool isLineSelected;
|
|
bool isStartingPointSelected;
|
|
bool isEndPointSelected;
|
|
bool isMidPointSelected;
|
|
|
|
|
|
SubElementType clickedOn;
|
|
SubElementType hovered;
|
|
bool rightClicked;
|
|
|
|
QString label;
|
|
|
|
private:
|
|
ViewProviderSketch* sketchView;
|
|
};
|
|
// clang-format off
|
|
|
|
class ElementFilterList: public QListWidget
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
explicit ElementFilterList(QWidget* parent = nullptr);
|
|
~ElementFilterList() override;
|
|
|
|
protected:
|
|
void changeEvent(QEvent* e) override;
|
|
void languageChange();
|
|
|
|
private:
|
|
using filterItemRepr =
|
|
std::pair<const char*, const int>;// {filter item text, filter item level}
|
|
inline static const std::vector<filterItemRepr> filterItems = {
|
|
{QT_TR_NOOP("Normal"), 0},
|
|
{QT_TR_NOOP("Construction"), 0},
|
|
{QT_TR_NOOP("Internal"), 0},
|
|
{QT_TR_NOOP("External"), 0},
|
|
{QT_TR_NOOP("All types"), 0},
|
|
{QT_TR_NOOP("Point"), 1},
|
|
{QT_TR_NOOP("Line"), 1},
|
|
{QT_TR_NOOP("Circle"), 1},
|
|
{QT_TR_NOOP("Ellipse"), 1},
|
|
{QT_TR_NOOP("Arc of circle"), 1},
|
|
{QT_TR_NOOP("Arc of ellipse"), 1},
|
|
{QT_TR_NOOP("Arc of hyperbola"), 1},
|
|
{QT_TR_NOOP("Arc of parabola"), 1},
|
|
{QT_TR_NOOP("B-Spline"), 1}};
|
|
};
|
|
}// namespace SketcherGui
|
|
|
|
class ElementWidgetIcons
|
|
{
|
|
|
|
private:
|
|
ElementWidgetIcons()
|
|
{
|
|
initIcons();
|
|
}
|
|
|
|
public:
|
|
ElementWidgetIcons(const ElementWidgetIcons&) = delete;
|
|
ElementWidgetIcons(ElementWidgetIcons&&) = delete;
|
|
ElementWidgetIcons& operator=(const ElementWidgetIcons&) = delete;
|
|
ElementWidgetIcons& operator=(ElementWidgetIcons&&) = delete;
|
|
|
|
static const QIcon&
|
|
getIcon(Base::Type type, Sketcher::PointPos pos,
|
|
ElementItem::GeometryState icontype = ElementItem::GeometryState::Normal)
|
|
{
|
|
static ElementWidgetIcons elementicons;
|
|
|
|
return elementicons.getIconImpl(type, pos, icontype);
|
|
}
|
|
|
|
private:
|
|
void initIcons()
|
|
{
|
|
|
|
icons.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(Part::GeomArcOfCircle::getClassTypeId()),
|
|
std::forward_as_tuple(
|
|
std::initializer_list<
|
|
std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
|
|
{Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Arc_Edge")},
|
|
{Sketcher::PointPos::start, getMultIcon("Sketcher_Element_Arc_StartingPoint")},
|
|
{Sketcher::PointPos::end, getMultIcon("Sketcher_Element_Arc_EndPoint")},
|
|
{Sketcher::PointPos::mid, getMultIcon("Sketcher_Element_Arc_MidPoint")}}));
|
|
|
|
icons.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(Part::GeomCircle::getClassTypeId()),
|
|
std::forward_as_tuple(
|
|
std::initializer_list<
|
|
std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
|
|
{Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Circle_Edge")},
|
|
{Sketcher::PointPos::mid, getMultIcon("Sketcher_Element_Circle_MidPoint")},
|
|
}));
|
|
|
|
icons.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(Part::GeomLineSegment::getClassTypeId()),
|
|
std::forward_as_tuple(
|
|
std::initializer_list<
|
|
std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
|
|
{Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Line_Edge")},
|
|
{Sketcher::PointPos::start, getMultIcon("Sketcher_Element_Line_StartingPoint")},
|
|
{Sketcher::PointPos::end, getMultIcon("Sketcher_Element_Line_EndPoint")},
|
|
}));
|
|
|
|
icons.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(Part::GeomPoint::getClassTypeId()),
|
|
std::forward_as_tuple(
|
|
std::initializer_list<
|
|
std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
|
|
{Sketcher::PointPos::start,
|
|
getMultIcon("Sketcher_Element_Point_StartingPoint")},
|
|
}));
|
|
|
|
icons.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(Part::GeomEllipse::getClassTypeId()),
|
|
std::forward_as_tuple(
|
|
std::initializer_list<
|
|
std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
|
|
{Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Ellipse_Edge_2")},
|
|
{Sketcher::PointPos::mid, getMultIcon("Sketcher_Element_Ellipse_CentrePoint")},
|
|
}));
|
|
|
|
icons.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(Part::GeomArcOfEllipse::getClassTypeId()),
|
|
std::forward_as_tuple(
|
|
std::initializer_list<
|
|
std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
|
|
{Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Elliptical_Arc_Edge")},
|
|
{Sketcher::PointPos::start,
|
|
getMultIcon("Sketcher_Element_Elliptical_Arc_Start_Point")},
|
|
{Sketcher::PointPos::end,
|
|
getMultIcon("Sketcher_Element_Elliptical_Arc_End_Point")},
|
|
{Sketcher::PointPos::mid,
|
|
getMultIcon("Sketcher_Element_Elliptical_Arc_Centre_Point")},
|
|
}));
|
|
|
|
icons.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(Part::GeomArcOfHyperbola::getClassTypeId()),
|
|
std::forward_as_tuple(
|
|
std::initializer_list<
|
|
std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
|
|
{Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Hyperbolic_Arc_Edge")},
|
|
{Sketcher::PointPos::start,
|
|
getMultIcon("Sketcher_Element_Hyperbolic_Arc_Start_Point")},
|
|
{Sketcher::PointPos::end,
|
|
getMultIcon("Sketcher_Element_Hyperbolic_Arc_End_Point")},
|
|
{Sketcher::PointPos::mid,
|
|
getMultIcon("Sketcher_Element_Hyperbolic_Arc_Centre_Point")},
|
|
}));
|
|
|
|
icons.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(Part::GeomArcOfParabola::getClassTypeId()),
|
|
std::forward_as_tuple(
|
|
std::initializer_list<
|
|
std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
|
|
{Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Parabolic_Arc_Edge")},
|
|
{Sketcher::PointPos::start,
|
|
getMultIcon("Sketcher_Element_Parabolic_Arc_Start_Point")},
|
|
{Sketcher::PointPos::end,
|
|
getMultIcon("Sketcher_Element_Parabolic_Arc_End_Point")},
|
|
{Sketcher::PointPos::mid,
|
|
getMultIcon("Sketcher_Element_Parabolic_Arc_Centre_Point")},
|
|
}));
|
|
|
|
icons.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(Part::GeomBSplineCurve::getClassTypeId()),
|
|
std::forward_as_tuple(
|
|
std::initializer_list<
|
|
std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
|
|
{Sketcher::PointPos::none, getMultIcon("Sketcher_Element_BSpline_Edge")},
|
|
{Sketcher::PointPos::start, getMultIcon("Sketcher_Element_BSpline_StartPoint")},
|
|
{Sketcher::PointPos::end, getMultIcon("Sketcher_Element_BSpline_EndPoint")},
|
|
}));
|
|
|
|
icons.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(Base::Type::badType()),
|
|
std::forward_as_tuple(
|
|
std::initializer_list<
|
|
std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
|
|
{Sketcher::PointPos::none,
|
|
getMultIcon("Sketcher_Element_SelectionTypeInvalid")},
|
|
}));
|
|
}
|
|
|
|
const QIcon& getIconImpl(Base::Type type, Sketcher::PointPos pos,
|
|
ElementItem::GeometryState icontype)
|
|
{
|
|
|
|
auto typekey = icons.find(type);
|
|
|
|
if (typekey == icons.end()) {// Not supported Geometry Type - Defaults to invalid icon
|
|
typekey = icons.find(Base::Type::badType());
|
|
pos = Sketcher::PointPos::none;
|
|
}
|
|
|
|
auto poskey = typekey->second.find(pos);
|
|
|
|
if (poskey == typekey->second.end()) {// invalid PointPos for type - Provide Invalid icon
|
|
typekey = icons.find(Base::Type::badType());
|
|
pos = Sketcher::PointPos::none;
|
|
poskey = typekey->second.find(pos);
|
|
}
|
|
|
|
if (icontype == ElementItem::GeometryState::Normal)
|
|
return std::get<0>(poskey->second);
|
|
else if (icontype == ElementItem::GeometryState::Construction)
|
|
return std::get<1>(poskey->second);
|
|
else if (icontype == ElementItem::GeometryState::External)
|
|
return std::get<2>(poskey->second);
|
|
else// internal alignment
|
|
return std::get<3>(poskey->second);
|
|
|
|
// We should never arrive here, as badtype, PointPos::none must exist.
|
|
throw Base::ValueError("Icon for Invalid is missing!!");
|
|
}
|
|
|
|
std::tuple<QIcon, QIcon, QIcon, QIcon> getMultIcon(const char* name)
|
|
{
|
|
int hue, sat, val, alp;
|
|
QIcon Normal = Gui::BitmapFactory().iconFromTheme(name);
|
|
QImage imgConstr(Normal.pixmap(qAsConst(Normal).availableSizes()[0]).toImage());
|
|
QImage imgExt(imgConstr);
|
|
QImage imgInt(imgConstr);
|
|
|
|
// Create construction/external/internal icons by changing colors.
|
|
for (int ix = 0; ix < imgConstr.width(); ix++) {
|
|
for (int iy = 0; iy < imgConstr.height(); iy++) {
|
|
QColor clr(imgConstr.pixelColor(ix, iy));
|
|
clr.getHsv(&hue, &sat, &val, &alp);
|
|
if (alp > 127 && hue >= 0) {
|
|
if (sat > 127 && (hue > 330 || hue < 30)) {// change the color of red points.
|
|
clr.setHsv((hue + 240) % 360, sat, val, alp);
|
|
imgConstr.setPixelColor(ix, iy, clr);
|
|
clr.setHsv((hue + 300) % 360, sat, val, alp);
|
|
imgExt.setPixelColor(ix, iy, clr);
|
|
clr.setHsv((hue + 60) % 360,
|
|
(int)(sat / 3),
|
|
std::min((int)(val * 8 / 7), 255),
|
|
alp);
|
|
imgInt.setPixelColor(ix, iy, clr);
|
|
}
|
|
else if (sat < 64 && val > 192) {// change the color of white edges.
|
|
clr.setHsv(240, (255 - sat), val, alp);
|
|
imgConstr.setPixel(ix, iy, clr.rgba());
|
|
clr.setHsv(300, (255 - sat), val, alp);
|
|
imgExt.setPixel(ix, iy, clr.rgba());
|
|
clr.setHsv(60, (int)(255 - sat) / 2, val, alp);
|
|
imgInt.setPixel(ix, iy, clr.rgba());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
QIcon Construction = QIcon(QPixmap::fromImage(imgConstr));
|
|
QIcon External = QIcon(QPixmap::fromImage(imgExt));
|
|
QIcon Internal = QIcon(QPixmap::fromImage(imgInt));
|
|
|
|
return std::make_tuple(Normal, Construction, External, Internal);
|
|
}
|
|
|
|
private:
|
|
std::map<Base::Type, std::map<Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>>
|
|
icons;
|
|
};
|
|
|
|
ElementView::ElementView(QWidget* parent)
|
|
: QListWidget(parent)
|
|
{
|
|
ElementItemDelegate* elementItemDelegate = new ElementItemDelegate(this);
|
|
setItemDelegate(elementItemDelegate);
|
|
|
|
QObject::connect(
|
|
elementItemDelegate, &ElementItemDelegate::itemHovered, this, &ElementView::onIndexHovered);
|
|
|
|
QObject::connect(
|
|
elementItemDelegate, &ElementItemDelegate::itemChecked, this, &ElementView::onIndexChecked);
|
|
}
|
|
|
|
ElementView::~ElementView()
|
|
{}
|
|
|
|
void ElementView::changeLayer(int layer)
|
|
{
|
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
|
|
|
if (!doc)
|
|
return;
|
|
|
|
doc->openTransaction("Geometry Layer Change");
|
|
std::vector<Gui::SelectionObject> sel = Gui::Selection().getSelectionEx(doc->getName());
|
|
for (std::vector<Gui::SelectionObject>::iterator ft = sel.begin(); ft != sel.end(); ++ft) {
|
|
auto sketchobject = dynamic_cast<Sketcher::SketchObject*>(ft->getObject());
|
|
|
|
auto geoids = getGeoIdsOfEdgesFromNames(sketchobject, ft->getSubNames());
|
|
|
|
auto geometry = sketchobject->Geometry.getValues();
|
|
auto newgeometry(geometry);
|
|
|
|
bool anychanged = false;
|
|
for (auto geoid : geoids) {
|
|
if (geoid
|
|
>= 0) {// currently only internal geometry can be changed from one layer to another
|
|
auto currentlayer = getSafeGeomLayerId(geometry[geoid]);
|
|
if (currentlayer != layer) {
|
|
auto geo = geometry[geoid]->clone();
|
|
setSafeGeomLayerId(geo, layer);
|
|
newgeometry[geoid] = geo;
|
|
anychanged = true;
|
|
}
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(
|
|
sketchobject,
|
|
QObject::tr("Unsupported visual layer operation"),
|
|
QObject::tr("It is currently unsupported to move external geometry to another "
|
|
"visual layer. External geometry will be omitted"));
|
|
}
|
|
}
|
|
|
|
if (anychanged) {
|
|
sketchobject->Geometry.setValues(std::move(newgeometry));
|
|
sketchobject->solve();
|
|
}
|
|
}
|
|
doc->commitTransaction();
|
|
}
|
|
|
|
void ElementView::changeLayer(ElementItem* item, int layer)
|
|
{
|
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
|
|
|
if (!doc) {
|
|
return;
|
|
}
|
|
|
|
doc->openTransaction("Geometry Layer Change");
|
|
|
|
auto sketchObject = item->getSketchObject();
|
|
|
|
auto geometry = sketchObject->Geometry.getValues();
|
|
auto newGeometry(geometry);
|
|
|
|
auto geoid = item->ElementNbr;
|
|
|
|
// currently only internal geometry can be changed from one layer to another
|
|
if (geoid >= 0) {
|
|
auto currentLayer = getSafeGeomLayerId(geometry[geoid]);
|
|
|
|
if (currentLayer != layer) {
|
|
auto geo = geometry[geoid]->clone();
|
|
setSafeGeomLayerId(geo, layer);
|
|
newGeometry[geoid] = geo;
|
|
|
|
sketchObject->Geometry.setValues(std::move(newGeometry));
|
|
sketchObject->solve();
|
|
}
|
|
}
|
|
else {
|
|
Gui::TranslatedUserWarning(
|
|
sketchObject,
|
|
QObject::tr("Unsupported visual layer operation"),
|
|
QObject::tr("It is currently unsupported to move external geometry to another "
|
|
"visual layer. External geometry will be omitted"));
|
|
}
|
|
|
|
doc->commitTransaction();
|
|
}
|
|
|
|
void ElementView::contextMenuEvent(QContextMenuEvent* event)
|
|
{
|
|
QMenu menu;
|
|
QList<QListWidgetItem*> items = selectedItems();
|
|
|
|
// NOTE: If extending this context menu, be sure to add the items to the translation block at
|
|
// the top of this file
|
|
|
|
// CONTEXT_ITEM(ICONSTR,NAMESTR,CMDSTR,FUNC,ACTSONSELECTION)
|
|
CONTEXT_ITEM("Constraint_PointOnPoint",
|
|
"Point Coincidence",
|
|
"Sketcher_ConstrainCoincident",
|
|
doPointCoincidence,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_PointOnObject",
|
|
"Point on Object",
|
|
"Sketcher_ConstrainPointOnObject",
|
|
doPointOnObjectConstraint,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_Vertical",
|
|
"Vertical Constraint",
|
|
"Sketcher_ConstrainVertical",
|
|
doVerticalConstraint,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_Horizontal",
|
|
"Horizontal Constraint",
|
|
"Sketcher_ConstrainHorizontal",
|
|
doHorizontalConstraint,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_Parallel",
|
|
"Parallel Constraint",
|
|
"Sketcher_ConstrainParallel",
|
|
doParallelConstraint,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_Perpendicular",
|
|
"Perpendicular Constraint",
|
|
"Sketcher_ConstrainPerpendicular",
|
|
doPerpendicularConstraint,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_Tangent",
|
|
"Tangent Constraint",
|
|
"Sketcher_ConstrainTangent",
|
|
doTangentConstraint,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_EqualLength",
|
|
"Equal Length",
|
|
"Sketcher_ConstrainEqual",
|
|
doEqualConstraint,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_Symmetric",
|
|
"Symmetric",
|
|
"Sketcher_ConstrainSymmetric",
|
|
doSymmetricConstraint,
|
|
true)
|
|
CONTEXT_ITEM(
|
|
"Constraint_Block", "Block Constraint", "Sketcher_ConstrainBlock", doBlockConstraint, true)
|
|
|
|
CONTEXT_ITEM(
|
|
"Constraint_Lock", "Lock Constraint", "Sketcher_ConstrainLock", doLockConstraint, true)
|
|
CONTEXT_ITEM("Constraint_HorizontalDistance",
|
|
"Horizontal Distance",
|
|
"Sketcher_ConstrainDistanceX",
|
|
doHorizontalDistance,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_VerticalDistance",
|
|
"Vertical Distance",
|
|
"Sketcher_ConstrainDistanceY",
|
|
doVerticalDistance,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_Length",
|
|
"Length Constraint",
|
|
"Sketcher_ConstrainDistance",
|
|
doLengthConstraint,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_Radius",
|
|
"Radius Constraint",
|
|
"Sketcher_ConstrainRadius",
|
|
doRadiusConstraint,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_Diameter",
|
|
"Diameter Constraint",
|
|
"Sketcher_ConstrainDiameter",
|
|
doDiameterConstraint,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_Radiam",
|
|
"Radiam Constraint",
|
|
"Sketcher_ConstrainRadiam",
|
|
doRadiamConstraint,
|
|
true)
|
|
CONTEXT_ITEM("Constraint_InternalAngle",
|
|
"Angle Constraint",
|
|
"Sketcher_ConstrainAngle",
|
|
doAngleConstraint,
|
|
true)
|
|
|
|
menu.addSeparator();
|
|
|
|
CONTEXT_ITEM("Sketcher_ToggleConstruction",
|
|
"Toggle construction geometry",
|
|
"Sketcher_ToggleConstruction",
|
|
doToggleConstruction,
|
|
true)
|
|
|
|
menu.addSeparator();
|
|
|
|
CONTEXT_ITEM("Sketcher_SelectConstraints",
|
|
"Select Constraints",
|
|
"Sketcher_SelectConstraints",
|
|
doSelectConstraints,
|
|
true)
|
|
CONTEXT_ITEM(
|
|
"Sketcher_SelectOrigin", "Select Origin", "Sketcher_SelectOrigin", doSelectOrigin, false)
|
|
CONTEXT_ITEM("Sketcher_SelectHorizontalAxis",
|
|
"Select Horizontal Axis",
|
|
"Sketcher_SelectHorizontalAxis",
|
|
doSelectHAxis,
|
|
false)
|
|
CONTEXT_ITEM("Sketcher_SelectVerticalAxis",
|
|
"Select Vertical Axis",
|
|
"Sketcher_SelectVerticalAxis",
|
|
doSelectVAxis,
|
|
false)
|
|
|
|
menu.addSeparator();
|
|
|
|
auto submenu = menu.addMenu(tr("Layer"));
|
|
|
|
auto addLayerAction = [submenu, this, items](auto&& name, int layernumber) {
|
|
auto action = submenu->addAction(std::forward<decltype(name)>(name), [this, layernumber]() {
|
|
changeLayer(layernumber);
|
|
});
|
|
action->setEnabled(!items.isEmpty());
|
|
return action;
|
|
};
|
|
|
|
addLayerAction(tr("Layer 0"), 0);
|
|
addLayerAction(tr("Layer 1"), 1);
|
|
addLayerAction(tr("Hidden"), 2);
|
|
|
|
|
|
menu.addSeparator();
|
|
|
|
QAction* remove = menu.addAction(tr("Delete"), this, &ElementView::deleteSelectedItems);
|
|
remove->setShortcut(QKeySequence(QKeySequence::Delete));
|
|
remove->setEnabled(!items.isEmpty());
|
|
|
|
menu.menuAction()->setIconVisibleInMenu(true);
|
|
|
|
menu.exec(event->globalPos());
|
|
}
|
|
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainCoincident", doPointCoincidence)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainPointOnObject", doPointOnObjectConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainVertical", doVerticalConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainHorizontal", doHorizontalConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainParallel", doParallelConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainPerpendicular", doPerpendicularConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainTangent", doTangentConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainEqual", doEqualConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainSymmetric", doSymmetricConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainBlock", doBlockConstraint)
|
|
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainLock", doLockConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainDistanceX", doHorizontalDistance)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainDistanceY", doVerticalDistance)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainDistance", doLengthConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainRadius", doRadiusConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainDiameter", doDiameterConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainRadiam", doRadiamConstraint)
|
|
CONTEXT_MEMBER_DEF("Sketcher_ConstrainAngle", doAngleConstraint)
|
|
|
|
CONTEXT_MEMBER_DEF("Sketcher_ToggleConstruction", doToggleConstruction)
|
|
|
|
CONTEXT_MEMBER_DEF("Sketcher_SelectConstraints", doSelectConstraints)
|
|
CONTEXT_MEMBER_DEF("Sketcher_SelectOrigin", doSelectOrigin)
|
|
CONTEXT_MEMBER_DEF("Sketcher_SelectHorizontalAxis", doSelectHAxis)
|
|
CONTEXT_MEMBER_DEF("Sketcher_SelectVerticalAxis", doSelectVAxis)
|
|
|
|
void ElementView::deleteSelectedItems()
|
|
{
|
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
|
if (!doc)
|
|
return;
|
|
|
|
doc->openTransaction("Delete element");
|
|
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 ElementView::onIndexHovered(QModelIndex index)
|
|
{
|
|
update(index);
|
|
|
|
Q_EMIT onItemHovered(itemFromIndex(index));
|
|
}
|
|
|
|
void ElementView::onIndexChecked(QModelIndex index, Qt::CheckState state)
|
|
{
|
|
auto item = itemFromIndex(index);
|
|
|
|
changeLayer(item, static_cast<int>(state == Qt::Checked ? ElementItem::Layer::Default : ElementItem::Layer::Hidden));
|
|
}
|
|
|
|
ElementItem* ElementView::itemFromIndex(const QModelIndex& index)
|
|
{
|
|
return static_cast<ElementItem*>(QListWidget::itemFromIndex(index));
|
|
}
|
|
|
|
// clang-format on
|
|
/* ElementItem delegate ---------------------------------------------------- */
|
|
ElementItemDelegate::ElementItemDelegate(ElementView* parent)
|
|
: QStyledItemDelegate(parent)
|
|
{ // This class relies on the parent being an ElementView, see getElementtItem
|
|
}
|
|
|
|
void ElementItemDelegate::paint(QPainter* painter,
|
|
const QStyleOptionViewItem& option,
|
|
const QModelIndex& index) const
|
|
{
|
|
ElementItem* item = getElementItem(index);
|
|
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
auto style = option.widget ? option.widget->style() : QApplication::style();
|
|
|
|
QStyleOptionViewItem itemOption = option;
|
|
|
|
initStyleOption(&itemOption, index);
|
|
|
|
if (item->isLineSelected || item->isStartingPointSelected || item->isEndPointSelected
|
|
|| item->isMidPointSelected) {
|
|
itemOption.state |= QStyle::State_Active;
|
|
}
|
|
|
|
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &itemOption, painter, option.widget);
|
|
|
|
drawSubControl(SubControl::CheckBox, painter, option, index);
|
|
drawSubControl(SubControl::LineSelect, painter, option, index);
|
|
drawSubControl(SubControl::StartSelect, painter, option, index);
|
|
drawSubControl(SubControl::EndSelect, painter, option, index);
|
|
drawSubControl(SubControl::MidSelect, painter, option, index);
|
|
drawSubControl(SubControl::Label, painter, option, index);
|
|
}
|
|
|
|
QRect ElementItemDelegate::subControlRect(SubControl element,
|
|
const QStyleOptionViewItem& option,
|
|
const QModelIndex& index) const
|
|
{
|
|
auto itemOption = option;
|
|
|
|
auto style = option.widget ? option.widget->style() : QApplication::style();
|
|
|
|
initStyleOption(&itemOption, index);
|
|
|
|
QRect checkBoxRect =
|
|
style->subElementRect(QStyle::SE_CheckBoxIndicator, &itemOption, option.widget);
|
|
|
|
checkBoxRect.moveTo(gap,
|
|
option.rect.top() + (option.rect.height() - checkBoxRect.height()) / 2);
|
|
|
|
if (element == SubControl::CheckBox) {
|
|
return checkBoxRect;
|
|
}
|
|
|
|
QRect selectRect =
|
|
style->subElementRect(QStyle::SE_ItemViewItemDecoration, &itemOption, option.widget)
|
|
.translated(checkBoxRect.right() + gap, 0);
|
|
|
|
unsigned pos = element - SubControl::LineSelect;
|
|
|
|
auto rect = selectRect.translated((selectRect.width() + gap) * pos, 0);
|
|
|
|
if (element != SubControl::Label) {
|
|
return rect;
|
|
}
|
|
|
|
rect.setRight(itemOption.rect.right());
|
|
|
|
return rect;
|
|
}
|
|
|
|
void ElementItemDelegate::drawSubControl(SubControl element,
|
|
QPainter* painter,
|
|
const QStyleOptionViewItem& option,
|
|
const QModelIndex& index) const
|
|
{
|
|
auto item = getElementItem(index);
|
|
auto style = option.widget ? option.widget->style() : QApplication::style();
|
|
|
|
auto rect = subControlRect(element, option, index);
|
|
|
|
auto mousePos = option.widget->mapFromGlobal(QCursor::pos());
|
|
auto isHovered = rect.contains(mousePos);
|
|
|
|
auto drawSelectIcon = [&](Sketcher::PointPos pos) {
|
|
auto icon = ElementWidgetIcons::getIcon(item->GeometryType, pos, item->State);
|
|
auto opacity = 0.4f;
|
|
|
|
if (isHovered) {
|
|
opacity = 0.8f;
|
|
}
|
|
|
|
if (item->isGeometrySelected(pos)) {
|
|
opacity = 1.0f;
|
|
}
|
|
|
|
painter->setOpacity(opacity);
|
|
painter->drawPixmap(rect, icon.pixmap(rect.size()));
|
|
};
|
|
|
|
painter->save();
|
|
|
|
switch (element) {
|
|
case SubControl::CheckBox: {
|
|
QStyleOptionButton checkboxOption;
|
|
|
|
checkboxOption.initFrom(option.widget);
|
|
|
|
checkboxOption.state |= QStyle::State_Enabled;
|
|
checkboxOption.rect = rect;
|
|
|
|
if (item->isVisible()) {
|
|
checkboxOption.state |= QStyle::State_On;
|
|
}
|
|
else {
|
|
checkboxOption.state |= QStyle::State_Off;
|
|
}
|
|
|
|
style->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck,
|
|
&checkboxOption,
|
|
painter,
|
|
option.widget);
|
|
|
|
break;
|
|
}
|
|
|
|
case LineSelect: {
|
|
drawSelectIcon(Sketcher::PointPos::none);
|
|
break;
|
|
}
|
|
|
|
case StartSelect: {
|
|
drawSelectIcon(Sketcher::PointPos::start);
|
|
break;
|
|
}
|
|
|
|
case EndSelect: {
|
|
drawSelectIcon(Sketcher::PointPos::end);
|
|
break;
|
|
}
|
|
|
|
case MidSelect: {
|
|
drawSelectIcon(Sketcher::PointPos::mid);
|
|
break;
|
|
}
|
|
|
|
case Label: {
|
|
QRect rect = subControlRect(SubControl::Label, option, index);
|
|
|
|
auto labelBoundingBox = painter->fontMetrics().tightBoundingRect(item->label);
|
|
|
|
painter->drawText(rect.x(),
|
|
option.rect.bottom()
|
|
- (option.rect.height() - labelBoundingBox.height()) / 2,
|
|
item->label);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
// clang-format off
|
|
|
|
bool ElementItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model,
|
|
const QStyleOptionViewItem& option, const QModelIndex& index)
|
|
{
|
|
auto item = getElementItem(index);
|
|
|
|
auto getSubElementType = [&](QPoint pos) {
|
|
if (subControlRect(SubControl::LineSelect, option, index).contains(pos)) {
|
|
return SubElementType::edge;
|
|
} else if (subControlRect(SubControl::StartSelect, option, index).contains(pos)) {
|
|
return SubElementType::start;
|
|
} else if (subControlRect(SubControl::EndSelect, option, index).contains(pos)) {
|
|
return SubElementType::end;
|
|
} else if (subControlRect(SubControl::MidSelect, option, index).contains(pos)) {
|
|
return SubElementType::mid;
|
|
} else {
|
|
// depending on geometry type by default we select either point or edge
|
|
return item->GeometryType == Part::GeomPoint::getClassTypeId() ? SubElementType::start : SubElementType::edge;
|
|
}
|
|
};
|
|
|
|
if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) {
|
|
auto mouseEvent = static_cast<QMouseEvent*>(event);
|
|
|
|
item->clickedOn = getSubElementType(mouseEvent->pos());
|
|
item->rightClicked = mouseEvent->button() == Qt::RightButton;
|
|
|
|
QRect checkboxRect = subControlRect(SubControl::CheckBox, option, index);
|
|
|
|
if (mouseEvent->button() == Qt::LeftButton && checkboxRect.contains(mouseEvent->pos())) {
|
|
Q_EMIT itemChecked(index, item->isVisible() ? Qt::Unchecked : Qt::Checked);
|
|
}
|
|
}
|
|
else if (event->type() == QEvent::MouseMove) {
|
|
auto mouseEvent = static_cast<QMouseEvent*>(event);
|
|
|
|
item->hovered = getSubElementType(mouseEvent->pos());
|
|
|
|
Q_EMIT itemHovered(index);
|
|
}
|
|
|
|
return QStyledItemDelegate::editorEvent(event, model, option, index);
|
|
}
|
|
const QWidget *w
|
|
ElementItem* ElementItemDelegate::getElementItem(const QModelIndex& index) const
|
|
{
|
|
ElementView* elementView = static_cast<ElementView*>(parent());
|
|
return elementView->itemFromIndex(index);
|
|
}
|
|
|
|
/* Filter element list widget ------------------------------------------------------ */
|
|
|
|
enum class GeoFilterType
|
|
{
|
|
NormalGeos,
|
|
ConstructionGeos,
|
|
InternalGeos,
|
|
ExternalGeos,
|
|
AllGeosTypes,
|
|
PointGeos,
|
|
LineGeos,
|
|
CircleGeos,
|
|
EllipseGeos,
|
|
ArcGeos,
|
|
ArcOfEllipseGeos,
|
|
HyperbolaGeos,
|
|
ParabolaGeos,
|
|
BSplineGeos
|
|
};
|
|
|
|
ElementFilterList::ElementFilterList(QWidget* parent)
|
|
: QListWidget(parent)
|
|
{
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher/General");
|
|
int filterState = hGrp->GetInt("ElementFilterState",
|
|
INT_MAX);// INT_MAX = 1111111111111111111111111111111 in binary.
|
|
|
|
for (auto const& filterItem : filterItems) {
|
|
Q_UNUSED(filterItem);
|
|
auto it = new QListWidgetItem();
|
|
it->setFlags(it->flags() | Qt::ItemIsUserCheckable);
|
|
|
|
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.
|
|
|
|
addItem(it);
|
|
}
|
|
languageChange();
|
|
|
|
// We need to fix the state of 'All' group checkbox in case it is partially checked.
|
|
int indexOfAllTypes = static_cast<int>(GeoFilterType::AllGeosTypes);
|
|
if (item(indexOfAllTypes)->checkState() == Qt::Unchecked) {
|
|
bool allUnchecked = true;
|
|
for (int i = indexOfAllTypes + 1; i < count(); i++) {
|
|
if (item(i)->checkState() == Qt::Checked) {
|
|
allUnchecked = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!allUnchecked)
|
|
item(indexOfAllTypes)->setCheckState(Qt::PartiallyChecked);
|
|
}
|
|
}
|
|
|
|
ElementFilterList::~ElementFilterList()
|
|
{}
|
|
|
|
void ElementFilterList::changeEvent(QEvent* e)
|
|
{
|
|
if (e->type() == QEvent::LanguageChange) {
|
|
languageChange();
|
|
}
|
|
QWidget::changeEvent(e);
|
|
}
|
|
|
|
void ElementFilterList::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);
|
|
}
|
|
}
|
|
|
|
|
|
/* TRANSLATOR SketcherGui::TaskSketcherElements */
|
|
|
|
TaskSketcherElements::TaskSketcherElements(ViewProviderSketch* sketchView)
|
|
: TaskBox(Gui::BitmapFactory().pixmap("document-new"), tr("Elements"), true, nullptr)
|
|
, sketchView(sketchView)
|
|
, ui(new Ui_TaskSketcherElements())
|
|
, focusItemIndex(-1)
|
|
, previouslySelectedItemIndex(-1)
|
|
, previouslyHoveredItemIndex(-1)
|
|
, previouslyHoveredType(SubElementType::none)
|
|
, isNamingBoxChecked(false)
|
|
{
|
|
// we need a separate container widget to add all controls to
|
|
proxy = new QWidget(this);
|
|
ui->setupUi(proxy);
|
|
#ifdef Q_OS_MAC
|
|
QString cmdKey = QString::fromUtf8("\xe2\x8c\x98");// U+2318
|
|
#else
|
|
// translate the text (it's offered by Qt's translation files)
|
|
// but avoid being picked up by lupdate
|
|
const char* ctrlKey = "Ctrl";
|
|
QString cmdKey = QShortcut::tr(ctrlKey);
|
|
#endif
|
|
Q_UNUSED(cmdKey)
|
|
|
|
ui->listWidgetElements->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
ui->listWidgetElements->setEditTriggers(QListWidget::NoEditTriggers);
|
|
ui->listWidgetElements->setMouseTracking(true);
|
|
|
|
createFilterButtonActions();
|
|
createSettingsButtonActions();
|
|
|
|
connectSignals();
|
|
|
|
this->groupLayout()->addWidget(proxy);
|
|
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher/General");
|
|
ui->filterBox->setChecked(hGrp->GetBool("ElementFilterEnabled", true));
|
|
ui->filterButton->setEnabled(ui->filterBox->isChecked());
|
|
|
|
slotElementsChanged();
|
|
}
|
|
|
|
TaskSketcherElements::~TaskSketcherElements()
|
|
{
|
|
connectionElementsChanged.disconnect();
|
|
}
|
|
|
|
void TaskSketcherElements::connectSignals()
|
|
{
|
|
// connecting the needed signals
|
|
QObject::connect(ui->listWidgetElements,
|
|
&ElementView::itemPressed,
|
|
this,
|
|
&TaskSketcherElements::onListWidgetElementsItemPressed);
|
|
QObject::connect(ui->listWidgetElements,
|
|
&ElementView::itemEntered,
|
|
this,
|
|
&TaskSketcherElements::onListWidgetElementsItemEntered);
|
|
QObject::connect(ui->listWidgetElements,
|
|
&ElementView::onItemHovered,
|
|
this,
|
|
&TaskSketcherElements::onListWidgetElementsMouseMoveOnItem);
|
|
QObject::connect(filterList,
|
|
&QListWidget::itemChanged,
|
|
this,
|
|
&TaskSketcherElements::onListMultiFilterItemChanged);
|
|
QObject::connect(ui->filterBox,
|
|
&QCheckBox::stateChanged,
|
|
this,
|
|
&TaskSketcherElements::onFilterBoxStateChanged);
|
|
QObject::connect(
|
|
ui->settingsButton, &QToolButton::clicked, ui->settingsButton, &QToolButton::showMenu);
|
|
QObject::connect(qAsConst(ui->settingsButton)->actions()[0],
|
|
&QAction::changed,
|
|
this,
|
|
&TaskSketcherElements::onSettingsExtendedInformationChanged);
|
|
QObject::connect(
|
|
ui->filterButton, &QToolButton::clicked, ui->filterButton, &QToolButton::showMenu);
|
|
|
|
//NOLINTBEGIN
|
|
connectionElementsChanged = sketchView->signalElementsChanged.connect(
|
|
std::bind(&SketcherGui::TaskSketcherElements::slotElementsChanged, this));
|
|
//NOLINTEND
|
|
}
|
|
|
|
/* filter functions --------------------------------------------------- */
|
|
|
|
void TaskSketcherElements::createFilterButtonActions()
|
|
{
|
|
auto* action = new QWidgetAction(this);
|
|
filterList = new ElementFilterList(this);
|
|
action->setDefaultWidget(filterList);
|
|
qAsConst(ui->filterButton)->addAction(action);
|
|
}
|
|
|
|
void TaskSketcherElements::onFilterBoxStateChanged(int val)
|
|
{
|
|
Q_UNUSED(val);
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher/General");
|
|
hGrp->SetBool("ElementFilterEnabled", ui->filterBox->checkState() == Qt::Checked);
|
|
|
|
ui->filterButton->setEnabled(ui->filterBox->checkState() == Qt::Checked);
|
|
slotElementsChanged();
|
|
}
|
|
|
|
void TaskSketcherElements::onListMultiFilterItemChanged(QListWidgetItem* item)
|
|
{
|
|
{
|
|
QSignalBlocker sigblk(filterList);
|
|
|
|
int index = filterList->row(item);
|
|
int indexOfAllTypes = static_cast<int>(GeoFilterType::AllGeosTypes);
|
|
|
|
if (index == indexOfAllTypes) {
|
|
for (int i = indexOfAllTypes + 1; i < filterList->count(); i++) {
|
|
filterList->item(i)->setCheckState(item->checkState());
|
|
}
|
|
}
|
|
else if (index > indexOfAllTypes) {
|
|
bool atLeastOneUnchecked = false;
|
|
bool atLeastOneChecked = false;
|
|
|
|
for (int i = indexOfAllTypes + 1; i < filterList->count(); i++) {
|
|
if (filterList->item(i)->checkState() == Qt::Checked)
|
|
atLeastOneChecked = true;
|
|
if (filterList->item(i)->checkState() == Qt::Unchecked)
|
|
atLeastOneUnchecked = true;
|
|
}
|
|
if (atLeastOneChecked && atLeastOneUnchecked)
|
|
filterList->item(indexOfAllTypes)->setCheckState(Qt::PartiallyChecked);
|
|
else if (atLeastOneUnchecked)
|
|
filterList->item(indexOfAllTypes)->setCheckState(Qt::Unchecked);
|
|
else if (atLeastOneChecked)
|
|
filterList->item(indexOfAllTypes)->setCheckState(Qt::Checked);
|
|
}
|
|
}
|
|
|
|
// Save the state of the filter.
|
|
int filterState = INT_MIN;// INT_MIN = 000000000000000000000000000000 in binary.
|
|
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;
|
|
}
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher/General");
|
|
hGrp->SetInt("ElementFilterState", filterState);
|
|
|
|
updateVisibility();
|
|
}
|
|
|
|
void TaskSketcherElements::setItemVisibility(QListWidgetItem* it)
|
|
{
|
|
ElementItem* item = static_cast<ElementItem*>(it);
|
|
|
|
if (ui->filterBox->checkState() == Qt::Unchecked) {
|
|
item->setHidden(false);
|
|
return;
|
|
}
|
|
|
|
using GeometryState = ElementItem::GeometryState;
|
|
|
|
if ((filterList->item(static_cast<int>(GeoFilterType::NormalGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->State == GeometryState::Normal)
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::ConstructionGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->State == GeometryState::Construction)
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::InternalGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->State == GeometryState::InternalAlignment)
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::ExternalGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->State == GeometryState::External)
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::PointGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->GeometryType == Part::GeomPoint::getClassTypeId())
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::LineGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->GeometryType == Part::GeomLineSegment::getClassTypeId())
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::CircleGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->GeometryType == Part::GeomCircle::getClassTypeId())
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::EllipseGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->GeometryType == Part::GeomEllipse::getClassTypeId())
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::ArcGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->GeometryType == Part::GeomArcOfCircle::getClassTypeId())
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::ArcOfEllipseGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId())
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::HyperbolaGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId())
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::ParabolaGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->GeometryType == Part::GeomArcOfParabola::getClassTypeId())
|
|
|| (filterList->item(static_cast<int>(GeoFilterType::BSplineGeos))->checkState()
|
|
== Qt::Unchecked
|
|
&& item->GeometryType == Part::GeomBSplineCurve::getClassTypeId())) {
|
|
item->setHidden(true);
|
|
return;
|
|
}
|
|
item->setHidden(false);
|
|
}
|
|
|
|
void TaskSketcherElements::updateVisibility()
|
|
{
|
|
for (int i = 0; i < ui->listWidgetElements->count(); i++) {
|
|
setItemVisibility(ui->listWidgetElements->item(i));
|
|
}
|
|
}
|
|
|
|
/*------------------*/
|
|
void TaskSketcherElements::onSelectionChanged(const Gui::SelectionChanges& msg)
|
|
{
|
|
std::string temp;
|
|
if (msg.Type == Gui::SelectionChanges::ClrSelection) {
|
|
clearWidget();
|
|
}
|
|
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) {
|
|
QString expr = QString::fromLatin1(msg.pSubName);
|
|
std::string shapetype(msg.pSubName);
|
|
// if-else edge vertex
|
|
if (shapetype.size() > 4 && shapetype.substr(0, 4) == "Edge") {
|
|
QRegularExpression rx(QString::fromLatin1("^Edge(\\d+)$"));
|
|
QRegularExpressionMatch match;
|
|
boost::ignore_unused(expr.indexOf(rx, 0, &match));
|
|
if (match.hasMatch()) {
|
|
bool ok;
|
|
int ElementId = match.captured(1).toInt(&ok) - 1;
|
|
if (ok) {
|
|
int countItems = ui->listWidgetElements->count();
|
|
for (int i = 0; i < countItems; i++) {
|
|
ElementItem* item =
|
|
static_cast<ElementItem*>(ui->listWidgetElements->item(i));
|
|
if (item->ElementNbr == ElementId) {
|
|
item->isLineSelected = select;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (shapetype.size() > 6 && shapetype.substr(0, 6) == "Vertex") {
|
|
QRegularExpression rx(QString::fromLatin1("^Vertex(\\d+)$"));
|
|
QRegularExpressionMatch match;
|
|
boost::ignore_unused(expr.indexOf(rx, 0, &match));
|
|
if (match.hasMatch()) {
|
|
bool ok;
|
|
int ElementId = match.captured(1).toInt(&ok) - 1;
|
|
if (ok) {
|
|
// Get the GeoID&Pos
|
|
int GeoId;
|
|
Sketcher::PointPos PosId;
|
|
sketchView->getSketchObject()->getGeoVertexIndex(
|
|
ElementId, GeoId, PosId);
|
|
|
|
int countItems = ui->listWidgetElements->count();
|
|
for (int i = 0; i < countItems; i++) {
|
|
ElementItem* item =
|
|
static_cast<ElementItem*>(ui->listWidgetElements->item(i));
|
|
if (item->ElementNbr == GeoId) {
|
|
switch (PosId) {
|
|
case Sketcher::PointPos::start:
|
|
item->isStartingPointSelected = select;
|
|
break;
|
|
case Sketcher::PointPos::end:
|
|
item->isEndPointSelected = select;
|
|
break;
|
|
case Sketcher::PointPos::mid:
|
|
item->isMidPointSelected = select;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// update the listwidget
|
|
{
|
|
QSignalBlocker sigblk(ui->listWidgetElements);
|
|
for (int i = 0; i < ui->listWidgetElements->count(); i++) {
|
|
ElementItem* item =
|
|
static_cast<ElementItem*>(ui->listWidgetElements->item(i));
|
|
if (item->isSelected())
|
|
item->setSelected(
|
|
false);// if already selected, we need to reset setSelected or it
|
|
// won't draw subelements correctly if selecting several.
|
|
item->setSelected(item->isLineSelected || item->isStartingPointSelected
|
|
|| item->isEndPointSelected || item->isMidPointSelected);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (msg.Type == Gui::SelectionChanges::SetSelection) {
|
|
// do nothing here
|
|
}
|
|
}
|
|
|
|
void TaskSketcherElements::onListWidgetElementsItemPressed(QListWidgetItem* it)
|
|
{
|
|
// We use itemPressed instead of previously used ItemSelectionChanged because if user click on
|
|
// already selected item, ItemSelectionChanged didn't trigger.
|
|
if (!it)
|
|
return;
|
|
|
|
ElementItem* itf = static_cast<ElementItem*>(it);
|
|
bool rightClickOnSelected = itf->rightClicked
|
|
&& (itf->isLineSelected || itf->isStartingPointSelected || itf->isEndPointSelected
|
|
|| itf->isMidPointSelected);
|
|
itf->rightClicked = false;
|
|
if (rightClickOnSelected)// if user right clicked on a selected item, change nothing.
|
|
return;
|
|
|
|
{
|
|
QSignalBlocker sigblk(ui->listWidgetElements);
|
|
|
|
bool multipleselection = false;
|
|
bool multipleconsecutiveselection = false;
|
|
if (QApplication::keyboardModifiers() == Qt::ControlModifier)
|
|
multipleselection = true;
|
|
if (QApplication::keyboardModifiers() == Qt::ShiftModifier)
|
|
multipleconsecutiveselection = true;
|
|
|
|
if (multipleselection
|
|
&& multipleconsecutiveselection) {// ctrl takes priority over shift functionality
|
|
multipleselection = true;
|
|
multipleconsecutiveselection = false;
|
|
}
|
|
|
|
std::vector<std::string> elementSubNames;
|
|
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();
|
|
|
|
for (int i = 0; i < ui->listWidgetElements->count(); i++) {
|
|
ElementItem* item = static_cast<ElementItem*>(ui->listWidgetElements->item(i));
|
|
|
|
if (!multipleselection && !multipleconsecutiveselection) {
|
|
// if not multiple selection, then all are disabled but the one that was just
|
|
// selected
|
|
item->isLineSelected = false;
|
|
item->isStartingPointSelected = false;
|
|
item->isEndPointSelected = false;
|
|
item->isMidPointSelected = false;
|
|
}
|
|
|
|
if (item == itf) {
|
|
|
|
if (item->clickedOn == SubElementType::mid
|
|
&& (item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomCircle::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomEllipse::getClassTypeId())) {
|
|
item->isMidPointSelected = !item->isMidPointSelected;
|
|
}
|
|
else if (item->clickedOn == SubElementType::start
|
|
&& (item->GeometryType == Part::GeomPoint::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomLineSegment::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomBSplineCurve::getClassTypeId())) {
|
|
item->isStartingPointSelected = !item->isStartingPointSelected;
|
|
}
|
|
else if (item->clickedOn == SubElementType::end
|
|
&& (item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomLineSegment::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomBSplineCurve::getClassTypeId())) {
|
|
item->isEndPointSelected = !item->isEndPointSelected;
|
|
}
|
|
else if (item->clickedOn == SubElementType::edge
|
|
&& item->GeometryType != Part::GeomPoint::getClassTypeId()) {
|
|
item->isLineSelected = !item->isLineSelected;
|
|
}
|
|
item->clickedOn = SubElementType::none;
|
|
}
|
|
else if (multipleconsecutiveselection && previouslySelectedItemIndex >= 0
|
|
&& !rightClickOnSelected
|
|
&& ((i > focusItemIndex && i < previouslySelectedItemIndex)
|
|
|| (i < focusItemIndex && i > previouslySelectedItemIndex))) {
|
|
if (item->GeometryType == Part::GeomPoint::getClassTypeId()) {
|
|
item->isStartingPointSelected = true;
|
|
}
|
|
else {
|
|
item->isLineSelected = true;
|
|
}
|
|
}
|
|
|
|
// first update the listwidget. Item is selected if at least one element of the geo is
|
|
// selected.
|
|
bool selected = item->isLineSelected || item->isStartingPointSelected
|
|
|| item->isEndPointSelected || item->isMidPointSelected;
|
|
|
|
{
|
|
QSignalBlocker sigblk(ui->listWidgetElements);
|
|
|
|
if (item->isSelected() && selected) {
|
|
item->setSelected(
|
|
false);// if already selected and changing or adding subelement, ensure
|
|
// selection change is triggered, which ensures timely repaint
|
|
item->setSelected(selected);
|
|
}
|
|
else {
|
|
item->setSelected(selected);
|
|
}
|
|
}
|
|
|
|
// now the scene
|
|
std::stringstream ss;
|
|
|
|
|
|
if (item->isLineSelected) {
|
|
ss << "Edge" << item->ElementNbr + 1;
|
|
elementSubNames.push_back(ss.str());
|
|
}
|
|
|
|
auto selectVertex = [&ss, &elementSubNames](bool subelementselected, int vertexid) {
|
|
if (subelementselected) {
|
|
int vertex;
|
|
ss.str(std::string());
|
|
vertex = vertexid;
|
|
if (vertex != -1) {
|
|
ss << "Vertex" << vertex + 1;
|
|
elementSubNames.push_back(ss.str());
|
|
}
|
|
}
|
|
};
|
|
|
|
selectVertex(item->isStartingPointSelected, item->StartingVertex);
|
|
selectVertex(item->isEndPointSelected, item->EndVertex);
|
|
selectVertex(item->isMidPointSelected, item->MidVertex);
|
|
}
|
|
|
|
if (!elementSubNames.empty()) {
|
|
Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), elementSubNames);
|
|
}
|
|
|
|
this->blockSelection(block);
|
|
}
|
|
|
|
if (focusItemIndex > -1 && focusItemIndex < ui->listWidgetElements->count())
|
|
previouslySelectedItemIndex = focusItemIndex;
|
|
|
|
ui->listWidgetElements->repaint();
|
|
}
|
|
|
|
void TaskSketcherElements::onListWidgetElementsItemEntered(QListWidgetItem* item)
|
|
{
|
|
ui->listWidgetElements->setFocus();
|
|
|
|
focusItemIndex = ui->listWidgetElements->row(item);
|
|
}
|
|
|
|
void TaskSketcherElements::onListWidgetElementsMouseMoveOnItem(QListWidgetItem* it)
|
|
{
|
|
ElementItem* item = static_cast<ElementItem*>(it);
|
|
|
|
if (!item
|
|
|| (ui->listWidgetElements->row(item) == previouslyHoveredItemIndex
|
|
&& item->hovered == previouslyHoveredType))
|
|
return;
|
|
|
|
Gui::Selection().rmvPreselect();
|
|
|
|
bool validmid = item->hovered == SubElementType::mid
|
|
&& (item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomCircle::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomEllipse::getClassTypeId());
|
|
|
|
bool validstartpoint = item->hovered == SubElementType::start
|
|
&& (item->GeometryType == Part::GeomPoint::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomLineSegment::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomBSplineCurve::getClassTypeId());
|
|
|
|
bool validendpoint = item->hovered == SubElementType::end
|
|
&& (item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomLineSegment::getClassTypeId()
|
|
|| item->GeometryType == Part::GeomBSplineCurve::getClassTypeId());
|
|
|
|
bool validedge = item->hovered == SubElementType::edge
|
|
&& item->GeometryType != Part::GeomPoint::getClassTypeId();
|
|
|
|
if (validmid || validstartpoint || validendpoint || validedge) {
|
|
std::string doc_name = sketchView->getSketchObject()->getDocument()->getName();
|
|
std::string obj_name = sketchView->getSketchObject()->getNameInDocument();
|
|
|
|
std::stringstream ss;
|
|
|
|
auto preselectvertex = [&](int geoid, Sketcher::PointPos pos) {
|
|
int vertex = sketchView->getSketchObject()->getVertexIndexGeoPos(geoid, pos);
|
|
if (vertex != -1) {
|
|
ss << "Vertex" << vertex + 1;
|
|
Gui::Selection().setPreselect(doc_name.c_str(), obj_name.c_str(), ss.str().c_str());
|
|
}
|
|
};
|
|
|
|
if (item->hovered == SubElementType::start)
|
|
preselectvertex(item->ElementNbr, Sketcher::PointPos::start);
|
|
else if (item->hovered == SubElementType::end)
|
|
preselectvertex(item->ElementNbr, Sketcher::PointPos::end);
|
|
else if (item->hovered == SubElementType::mid)
|
|
preselectvertex(item->ElementNbr, Sketcher::PointPos::mid);
|
|
else if (item->hovered == SubElementType::edge) {
|
|
ss << "Edge" << item->ElementNbr + 1;
|
|
Gui::Selection().setPreselect(doc_name.c_str(), obj_name.c_str(), ss.str().c_str());
|
|
}
|
|
}
|
|
|
|
previouslyHoveredItemIndex = ui->listWidgetElements->row(item);
|
|
previouslyHoveredType = item->hovered;
|
|
}
|
|
|
|
void TaskSketcherElements::leaveEvent(QEvent* event)
|
|
{
|
|
Q_UNUSED(event);
|
|
Gui::Selection().rmvPreselect();
|
|
ui->listWidgetElements->clearFocus();
|
|
}
|
|
|
|
void TaskSketcherElements::slotElementsChanged()
|
|
{
|
|
assert(sketchView);
|
|
// Build up ListView with the elements
|
|
Sketcher::SketchObject* sketch = sketchView->getSketchObject();
|
|
const std::vector<Part::Geometry*>& vals = sketch->Geometry.getValues();
|
|
|
|
ui->listWidgetElements->clear();
|
|
|
|
using GeometryState = ElementItem::GeometryState;
|
|
|
|
int i = 1;
|
|
for (std::vector<Part::Geometry*>::const_iterator it = vals.begin(); it != vals.end();
|
|
++it, ++i) {
|
|
Base::Type type = (*it)->getTypeId();
|
|
GeometryState state = GeometryState::Normal;
|
|
|
|
bool construction = Sketcher::GeometryFacade::getConstruction(*it);
|
|
bool internalAligned = Sketcher::GeometryFacade::isInternalAligned(*it);
|
|
|
|
auto layerId = getSafeGeomLayerId(*it);
|
|
|
|
if (internalAligned)
|
|
state = GeometryState::InternalAlignment;
|
|
else if (construction)// Caution, internalAligned geos are construction too. So the 'if' and
|
|
// 'else if' cannot be swapped.
|
|
state = GeometryState::Construction;
|
|
|
|
auto IdInformation = [this, i, layerId]() {
|
|
if (sketchView->VisualLayerList.getSize() > 1)
|
|
return QString::fromLatin1("(Edge%1#ID%2#VL%3)").arg(i).arg(i - 1).arg(layerId);
|
|
else
|
|
return QString::fromLatin1("(Edge%1#ID%2)").arg(i).arg(i - 1);
|
|
};
|
|
|
|
ElementItem* itemN = new ElementItem(
|
|
i - 1,
|
|
sketchView->getSketchObject()->getVertexIndexGeoPos(i - 1, Sketcher::PointPos::start),
|
|
sketchView->getSketchObject()->getVertexIndexGeoPos(i - 1, Sketcher::PointPos::mid),
|
|
sketchView->getSketchObject()->getVertexIndexGeoPos(i - 1, Sketcher::PointPos::end),
|
|
type,
|
|
state,
|
|
type == Part::GeomPoint::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Point") + IdInformation())
|
|
+ (construction
|
|
? (QString::fromLatin1("-") + tr("Construction"))
|
|
: (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
|
|
: QString::fromLatin1("")))
|
|
: (QString::fromLatin1("%1-").arg(i) + tr("Point")))
|
|
: type == Part::GeomLineSegment::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Line") + IdInformation())
|
|
+ (construction
|
|
? (QString::fromLatin1("-") + tr("Construction"))
|
|
: (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
|
|
: QString::fromLatin1("")))
|
|
: (QString::fromLatin1("%1-").arg(i) + tr("Line")))
|
|
: type == Part::GeomArcOfCircle::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Arc") + IdInformation())
|
|
+ (construction
|
|
? (QString::fromLatin1("-") + tr("Construction"))
|
|
: (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
|
|
: QString::fromLatin1("")))
|
|
: (QString::fromLatin1("%1-").arg(i) + tr("Arc")))
|
|
: type == Part::GeomCircle::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Circle") + IdInformation())
|
|
+ (construction
|
|
? (QString::fromLatin1("-") + tr("Construction"))
|
|
: (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
|
|
: QString::fromLatin1("")))
|
|
: (QString::fromLatin1("%1-").arg(i) + tr("Circle")))
|
|
: type == Part::GeomEllipse::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Ellipse") + IdInformation())
|
|
+ (construction
|
|
? (QString::fromLatin1("-") + tr("Construction"))
|
|
: (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
|
|
: QString::fromLatin1("")))
|
|
: (QString::fromLatin1("%1-").arg(i) + tr("Ellipse")))
|
|
: type == Part::GeomArcOfEllipse::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Elliptical Arc") + IdInformation())
|
|
+ (construction
|
|
? (QString::fromLatin1("-") + tr("Construction"))
|
|
: (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
|
|
: QString::fromLatin1("")))
|
|
: (QString::fromLatin1("%1-").arg(i) + tr("Elliptical Arc")))
|
|
: type == Part::GeomArcOfHyperbola::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Hyperbolic Arc") + IdInformation())
|
|
+ (construction
|
|
? (QString::fromLatin1("-") + tr("Construction"))
|
|
: (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
|
|
: QString::fromLatin1("")))
|
|
: (QString::fromLatin1("%1-").arg(i) + tr("Hyperbolic Arc")))
|
|
: type == Part::GeomArcOfParabola::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Parabolic Arc") + IdInformation())
|
|
+ (construction
|
|
? (QString::fromLatin1("-") + tr("Construction"))
|
|
: (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
|
|
: QString::fromLatin1("")))
|
|
: (QString::fromLatin1("%1-").arg(i) + tr("Parabolic Arc")))
|
|
: type == Part::GeomBSplineCurve::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("BSpline") + IdInformation())
|
|
+ (construction
|
|
? (QString::fromLatin1("-") + tr("Construction"))
|
|
: (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
|
|
: QString::fromLatin1("")))
|
|
: (QString::fromLatin1("%1-").arg(i) + tr("BSpline")))
|
|
: (isNamingBoxChecked ? (tr("Other") + IdInformation())
|
|
+ (construction
|
|
? (QString::fromLatin1("-") + tr("Construction"))
|
|
: (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
|
|
: QString::fromLatin1("")))
|
|
: (QString::fromLatin1("%1-").arg(i) + tr("Other"))),
|
|
sketchView);
|
|
|
|
ui->listWidgetElements->addItem(itemN);
|
|
|
|
setItemVisibility(itemN);
|
|
}
|
|
|
|
const std::vector<Part::Geometry*>& ext_vals =
|
|
sketchView->getSketchObject()->getExternalGeometry();
|
|
|
|
const std::vector<App::DocumentObject*> linkobjs =
|
|
sketchView->getSketchObject()->ExternalGeometry.getValues();
|
|
const std::vector<std::string> linksubs =
|
|
sketchView->getSketchObject()->ExternalGeometry.getSubValues();
|
|
|
|
int j = 1;
|
|
for (std::vector<Part::Geometry*>::const_iterator it = ext_vals.begin(); it != ext_vals.end();
|
|
++it, ++i, ++j) {
|
|
Base::Type type = (*it)->getTypeId();
|
|
|
|
if (j > 2) {// we do not want the H and V axes
|
|
|
|
auto layerId = getSafeGeomLayerId(*it);
|
|
|
|
auto IdInformation = [this, j, layerId](bool link) {
|
|
if (sketchView->VisualLayerList.getSize() > 1) {
|
|
if (link) {
|
|
return QString::fromLatin1("(ExternalEdge%1#ID%2#VL%3, ")
|
|
.arg(j - 2)
|
|
.arg(-j)
|
|
.arg(layerId);
|
|
}
|
|
else {
|
|
return QString::fromLatin1("(ExternalEdge%1#ID%2#VL%3)")
|
|
.arg(j - 2)
|
|
.arg(-j)
|
|
.arg(layerId);
|
|
}
|
|
}
|
|
else {
|
|
if (link) {
|
|
return QString::fromLatin1("(ExternalEdge%1#ID%2, ").arg(j - 2).arg(-j);
|
|
}
|
|
else {
|
|
return QString::fromLatin1("(ExternalEdge%1#ID%2)").arg(j - 2).arg(-j);
|
|
}
|
|
}
|
|
};
|
|
|
|
QString linkname;
|
|
|
|
if (isNamingBoxChecked) {
|
|
if (size_t(j - 3) < linkobjs.size() && size_t(j - 3) < linksubs.size()) {
|
|
linkname = IdInformation(true)
|
|
+ QString::fromUtf8(linkobjs[j - 3]->getNameInDocument())
|
|
+ QString::fromLatin1(".") + QString::fromUtf8(linksubs[j - 3].c_str())
|
|
+ QString::fromLatin1(")");
|
|
}
|
|
else {
|
|
linkname = IdInformation(false);
|
|
}
|
|
}
|
|
|
|
GeometryState state = GeometryState::External;
|
|
|
|
ElementItem* itemN = new ElementItem(
|
|
-j,
|
|
sketchView->getSketchObject()->getVertexIndexGeoPos(-j, Sketcher::PointPos::start),
|
|
sketchView->getSketchObject()->getVertexIndexGeoPos(-j, Sketcher::PointPos::mid),
|
|
sketchView->getSketchObject()->getVertexIndexGeoPos(-j, Sketcher::PointPos::end),
|
|
type,
|
|
state,
|
|
type == Part::GeomPoint::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Point") + linkname)
|
|
: (QString::fromLatin1("%1-").arg(i - 2) + tr("Point")))
|
|
: type == Part::GeomLineSegment::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Line") + linkname)
|
|
: (QString::fromLatin1("%1-").arg(i - 2) + tr("Line")))
|
|
: type == Part::GeomArcOfCircle::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Arc") + linkname)
|
|
: (QString::fromLatin1("%1-").arg(i - 2) + tr("Arc")))
|
|
: type == Part::GeomCircle::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Circle") + linkname)
|
|
: (QString::fromLatin1("%1-").arg(i - 2) + tr("Circle")))
|
|
: type == Part::GeomEllipse::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("Ellipse") + linkname)
|
|
: (QString::fromLatin1("%1-").arg(i - 2) + tr("Ellipse")))
|
|
: type == Part::GeomArcOfEllipse::getClassTypeId()
|
|
? (isNamingBoxChecked
|
|
? (tr("Elliptical Arc") + linkname)
|
|
: (QString::fromLatin1("%1-").arg(i - 2) + tr("Elliptical Arc")))
|
|
: type == Part::GeomArcOfHyperbola::getClassTypeId()
|
|
? (isNamingBoxChecked
|
|
? (tr("Hyperbolic Arc") + linkname)
|
|
: (QString::fromLatin1("%1-").arg(i - 2) + tr("Hyperbolic Arc")))
|
|
: type == Part::GeomArcOfParabola::getClassTypeId()
|
|
? (isNamingBoxChecked
|
|
? (tr("Parabolic Arc") + linkname)
|
|
: (QString::fromLatin1("%1-").arg(i - 2) + tr("Parabolic Arc")))
|
|
: type == Part::GeomBSplineCurve::getClassTypeId()
|
|
? (isNamingBoxChecked ? (tr("BSpline") + linkname)
|
|
: (QString::fromLatin1("%1-").arg(i - 2) + tr("BSpline")))
|
|
: (isNamingBoxChecked ? (tr("Other") + linkname)
|
|
: (QString::fromLatin1("%1-").arg(i - 2) + tr("Other"))),
|
|
sketchView);
|
|
|
|
ui->listWidgetElements->addItem(itemN);
|
|
|
|
setItemVisibility(itemN);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TaskSketcherElements::clearWidget()
|
|
{
|
|
{
|
|
QSignalBlocker sigblk(ui->listWidgetElements);
|
|
ui->listWidgetElements->clearSelection();
|
|
}
|
|
|
|
// update widget
|
|
int countItems = ui->listWidgetElements->count();
|
|
for (int i = 0; i < countItems; i++) {
|
|
ElementItem* item = static_cast<ElementItem*>(ui->listWidgetElements->item(i));
|
|
|
|
item->isLineSelected = false;
|
|
item->isStartingPointSelected = false;
|
|
item->isEndPointSelected = false;
|
|
item->isMidPointSelected = false;
|
|
}
|
|
}
|
|
|
|
void TaskSketcherElements::changeEvent(QEvent* e)
|
|
{
|
|
TaskBox::changeEvent(e);
|
|
if (e->type() == QEvent::LanguageChange) {
|
|
ui->retranslateUi(proxy);
|
|
}
|
|
}
|
|
|
|
/* Settings menu ==================================================*/
|
|
void TaskSketcherElements::createSettingsButtonActions()
|
|
{
|
|
QAction* action = new QAction(tr("Extended information"), this);
|
|
|
|
action->setCheckable(true);
|
|
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher/Elements");
|
|
{
|
|
QSignalBlocker block(this);
|
|
action->setChecked(hGrp->GetBool("ExtendedNaming", false));
|
|
}
|
|
|
|
ui->settingsButton->addAction(action);
|
|
|
|
isNamingBoxChecked = hGrp->GetBool("ExtendedNaming", false);
|
|
}
|
|
|
|
void TaskSketcherElements::onSettingsExtendedInformationChanged()
|
|
{
|
|
QList<QAction*> acts = ui->settingsButton->actions();
|
|
isNamingBoxChecked = acts[0]->isChecked();
|
|
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Sketcher/Elements");
|
|
hGrp->SetBool("ExtendedNaming", isNamingBoxChecked);
|
|
|
|
slotElementsChanged();
|
|
}
|
|
|
|
#include "TaskSketcherElements.moc"// For Delegate as it is QOBJECT
|
|
#include "moc_TaskSketcherElements.cpp"
|
|
// clang-format on
|