FreeCAD

Форк
0
/
TaskSketcherElements.cpp 
2068 строк · 86.7 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2014 Abdullah Tahiri <abdullah.tahiri.yo@gmail.com>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
6
 *   This library is free software; you can redistribute it and/or         *
7
 *   modify it under the terms of the GNU Library General Public           *
8
 *   License as published by the Free Software Foundation; either          *
9
 *   version 2 of the License, or (at your option) any later version.      *
10
 *                                                                         *
11
 *   This library  is distributed in the hope that it will be useful,      *
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14
 *   GNU Library General Public License for more details.                  *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Library General Public     *
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
18
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
19
 *   Suite 330, Boston, MA  02111-1307, USA                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
#include <QContextMenuEvent>
26
#include <QImage>
27
#include <QMenu>
28
#include <QPainter>
29
#include <QPixmap>
30
#include <QRegularExpression>
31
#include <QRegularExpressionMatch>
32
#include <QShortcut>
33
#include <QString>
34
#include <QWidgetAction>
35
#include <boost/core/ignore_unused.hpp>
36
#endif
37

38
#include <App/Application.h>
39
#include <App/Document.h>
40
#include <App/DocumentObject.h>
41
#include <Gui/Application.h>
42
#include <Gui/BitmapFactory.h>
43
#include <Gui/Command.h>
44
#include <Gui/Notifications.h>
45
#include <Gui/Selection.h>
46
#include <Gui/SelectionObject.h>
47
#include <Gui/ViewProvider.h>
48
#include <Mod/Sketcher/App/GeometryFacade.h>
49
#include <Mod/Sketcher/App/SketchObject.h>
50

51
#include "TaskSketcherElements.h"
52
#include "Utils.h"
53
#include "ViewProviderSketch.h"
54
#include "ui_TaskSketcherElements.h"
55

56
// clang-format off
57
using namespace SketcherGui;
58
using namespace Gui::TaskView;
59

60
// Translation block for context menu: do not remove
61
#if 0
62
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Point Coincidence");
63
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Point on Object");
64
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Vertical Constraint");
65
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Horizontal Constraint");
66
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Parallel Constraint");
67
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Perpendicular Constraint");
68
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Tangent Constraint");
69
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Equal Length");
70
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Symmetric");
71
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Block Constraint");
72
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Lock Constraint");
73
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Horizontal Distance");
74
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Vertical Distance");
75
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Length Constraint");
76
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Radius Constraint");
77
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Diameter Constraint");
78
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Radiam Constraint");
79
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Angle Constraint");
80
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Toggle construction geometry");
81
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Select Constraints");
82
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Select Origin");
83
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Select Horizontal Axis");
84
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Select Vertical Axis");
85
#endif
86

87
/// Inserts a QAction into an existing menu
88
/// ICONSTR is the string of the icon in the resource file
89
/// NAMESTR is the text appearing in the contextual menuAction
90
/// CMDSTR is the string registered in the commandManager
91
/// FUNC is the name of the member function to be executed on selection of the menu item
92
/// ACTSONSELECTION is a true/false value to activate the command only if a selection is made
93
#define CONTEXT_ITEM(ICONSTR, NAMESTR, CMDSTR, FUNC, ACTSONSELECTION)                              \
94
    QIcon icon_##FUNC(Gui::BitmapFactory().pixmap(ICONSTR));                                       \
95
    QAction* constr_##FUNC = menu.addAction(icon_##FUNC, tr(NAMESTR), this, SLOT(FUNC()));         \
96
    constr_##FUNC->setShortcut(QKeySequence(QString::fromUtf8(                                     \
97
        Gui::Application::Instance->commandManager().getCommandByName(CMDSTR)->getAccel())));      \
98
    if (ACTSONSELECTION)                                                                           \
99
        constr_##FUNC->setEnabled(!items.isEmpty());                                               \
100
    else                                                                                           \
101
        constr_##FUNC->setEnabled(true);
102

103
/// Defines the member function corresponding to the CONTEXT_ITEM macro
104
#define CONTEXT_MEMBER_DEF(CMDSTR, FUNC)                                                           \
105
    void ElementView::FUNC()                                                                       \
106
    {                                                                                              \
107
        Gui::Application::Instance->commandManager().runCommandByName(CMDSTR);                     \
108
    }
109

110

111
namespace SketcherGui
112
{
113

114
class ElementItemDelegate: public QStyledItemDelegate
115
{
116
    Q_OBJECT
117
public:
118
    /// Enum containing all controls rendered in this item. Controls in that enum MUST be in order.
119
    enum SubControl : int {
120
        CheckBox,
121
        LineSelect,
122
        StartSelect,
123
        EndSelect,
124
        MidSelect,
125
        Label
126
    };
127

128
    explicit ElementItemDelegate(ElementView* parent);
129
    ~ElementItemDelegate() override = default;
130

131
    void paint(QPainter* painter, const QStyleOptionViewItem& option,
132
               const QModelIndex& index) const override;
133
    bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option,
134
                     const QModelIndex& index) override;
135

136
    ElementItem* getElementItem(const QModelIndex& index) const;
137

138
    QRect subControlRect(SubControl element, const QStyleOptionViewItem& option, const QModelIndex& index) const;
139
    void drawSubControl(SubControl element, QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
140

141
    const int gap = 4; // 4px of spacing between consecutive elements
142

143
Q_SIGNALS:
144
    void itemHovered(QModelIndex);
145
    void itemChecked(QModelIndex, Qt::CheckState state);
146
};
147

148
// clang-format on
149
// helper class to store additional information about the listWidget entry.
150
class ElementItem: public QListWidgetItem
151
{
152
public:
153
    enum class GeometryState
154
    {
155
        Normal,
156
        Construction,
157
        InternalAlignment,
158
        External
159
    };
160

161
    enum class Layer
162
    {
163
        Default = 0,
164
        Discontinuous = 1,
165
        Hidden = 2,
166
    };
167

168
    ElementItem(int elementnr,
169
                int startingVertex,
170
                int midVertex,
171
                int endVertex,
172
                Base::Type geometryType,
173
                GeometryState state,
174
                const QString& lab,
175
                ViewProviderSketch* sketchView)
176
        : ElementNbr(elementnr)
177
        , StartingVertex(startingVertex)
178
        , MidVertex(midVertex)
179
        , EndVertex(endVertex)
180
        , GeometryType(std::move(geometryType))
181
        , State(state)
182
        , isLineSelected(false)
183
        , isStartingPointSelected(false)
184
        , isEndPointSelected(false)
185
        , isMidPointSelected(false)
186
        , clickedOn(SubElementType::none)
187
        , hovered(SubElementType::none)
188
        , rightClicked(false)
189
        , label(lab)
190
        , sketchView(sketchView)
191
    {}
192

193
    ~ElementItem() override
194
    {}
195

196
    bool canBeHidden() const
197
    {
198
        return State != GeometryState::External;
199
    }
200

201
    bool isVisible() const
202
    {
203
        if (State != GeometryState::External) {
204
            const auto geo = sketchView->getSketchObject()->getGeometry(ElementNbr);
205
            if (geo) {
206
                auto layer = getSafeGeomLayerId(geo);
207

208
                return layer != static_cast<unsigned int>(Layer::Hidden);
209
            }
210
        }
211

212
        // 1. external geometry currently is always visible.
213
        // 2. if internal and ElementNbr is out of range, the element
214
        // needs to be updated and the return value is not important.
215
        return true;
216
    }
217

218
    QVariant data(int role) const override
219
    {
220
        // In order for content-box to include size of the 4 geometry icons we need to provide
221
        // Qt with information about decoration (icon) size. This is hack to work around Qt
222
        // limitation of not knowing about padding, border and margin boxes of stylesheets
223
        // thus being unable to provide proper sizeHint for stylesheets to render correctly
224
        if (role == Qt::DecorationRole) {
225
            auto size = listWidget()->iconSize();
226

227
            return QIcon(QPixmap(size));
228
        }
229

230
        return QListWidgetItem::data(role);
231
    }
232

233
    bool isGeometrySelected(Sketcher::PointPos pos) const
234
    {
235
        switch (pos) {
236
            case Sketcher::PointPos::none:
237
                return isLineSelected;
238
            case Sketcher::PointPos::start:
239
                return isStartingPointSelected;
240
            case Sketcher::PointPos::end:
241
                return isEndPointSelected;
242
            case Sketcher::PointPos::mid:
243
                return isMidPointSelected;
244
            default:
245
                return false;
246
        }
247
    }
248

249
    bool isGeometryPreselected(Sketcher::PointPos pos) const
250
    {
251
        switch (pos) {
252
            case Sketcher::PointPos::none:
253
                return hovered == SubElementType::edge;
254
            case Sketcher::PointPos::start:
255
                return hovered == SubElementType::start;
256
            case Sketcher::PointPos::end:
257
                return hovered == SubElementType::end;
258
            case Sketcher::PointPos::mid:
259
                return hovered == SubElementType::mid;
260
            default:
261
                return false;
262
        }
263
    }
264

265
    Sketcher::SketchObject* getSketchObject() const
266
    {
267
        return sketchView->getSketchObject();
268
    }
269

270
    int ElementNbr;
271
    int StartingVertex;
272
    int MidVertex;
273
    int EndVertex;
274

275
    Base::Type GeometryType;
276
    GeometryState State;
277

278
    bool isLineSelected;
279
    bool isStartingPointSelected;
280
    bool isEndPointSelected;
281
    bool isMidPointSelected;
282

283

284
    SubElementType clickedOn;
285
    SubElementType hovered;
286
    bool rightClicked;
287

288
    QString label;
289

290
private:
291
    ViewProviderSketch* sketchView;
292
};
293
// clang-format off
294

295
class ElementFilterList: public QListWidget
296
{
297
    Q_OBJECT
298

299
public:
300
    explicit ElementFilterList(QWidget* parent = nullptr);
301
    ~ElementFilterList() override;
302

303
protected:
304
    void changeEvent(QEvent* e) override;
305
    void languageChange();
306

307
private:
308
    using filterItemRepr =
309
        std::pair<const char*, const int>;// {filter item text, filter item level}
310
    inline static const std::vector<filterItemRepr> filterItems = {
311
        {QT_TR_NOOP("Normal"), 0},
312
        {QT_TR_NOOP("Construction"), 0},
313
        {QT_TR_NOOP("Internal"), 0},
314
        {QT_TR_NOOP("External"), 0},
315
        {QT_TR_NOOP("All types"), 0},
316
        {QT_TR_NOOP("Point"), 1},
317
        {QT_TR_NOOP("Line"), 1},
318
        {QT_TR_NOOP("Circle"), 1},
319
        {QT_TR_NOOP("Ellipse"), 1},
320
        {QT_TR_NOOP("Arc of circle"), 1},
321
        {QT_TR_NOOP("Arc of ellipse"), 1},
322
        {QT_TR_NOOP("Arc of hyperbola"), 1},
323
        {QT_TR_NOOP("Arc of parabola"), 1},
324
        {QT_TR_NOOP("B-spline"), 1}};
325
};
326
}// namespace SketcherGui
327

328
class ElementWidgetIcons
329
{
330

331
private:
332
    ElementWidgetIcons()
333
    {
334
        initIcons();
335
    }
336

337
public:
338
    ElementWidgetIcons(const ElementWidgetIcons&) = delete;
339
    ElementWidgetIcons(ElementWidgetIcons&&) = delete;
340
    ElementWidgetIcons& operator=(const ElementWidgetIcons&) = delete;
341
    ElementWidgetIcons& operator=(ElementWidgetIcons&&) = delete;
342

343
    static const QIcon&
344
    getIcon(Base::Type type, Sketcher::PointPos pos,
345
            ElementItem::GeometryState icontype = ElementItem::GeometryState::Normal)
346
    {
347
        static ElementWidgetIcons elementicons;
348

349
        return elementicons.getIconImpl(type, pos, icontype);
350
    }
351

352
private:
353
    void initIcons()
354
    {
355

356
        icons.emplace(
357
            std::piecewise_construct,
358
            std::forward_as_tuple(Part::GeomArcOfCircle::getClassTypeId()),
359
            std::forward_as_tuple(
360
                std::initializer_list<
361
                    std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
362
                    {Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Arc_Edge")},
363
                    {Sketcher::PointPos::start, getMultIcon("Sketcher_Element_Arc_StartingPoint")},
364
                    {Sketcher::PointPos::end, getMultIcon("Sketcher_Element_Arc_EndPoint")},
365
                    {Sketcher::PointPos::mid, getMultIcon("Sketcher_Element_Arc_MidPoint")}}));
366

367
        icons.emplace(
368
            std::piecewise_construct,
369
            std::forward_as_tuple(Part::GeomCircle::getClassTypeId()),
370
            std::forward_as_tuple(
371
                std::initializer_list<
372
                    std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
373
                    {Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Circle_Edge")},
374
                    {Sketcher::PointPos::mid, getMultIcon("Sketcher_Element_Circle_MidPoint")},
375
                }));
376

377
        icons.emplace(
378
            std::piecewise_construct,
379
            std::forward_as_tuple(Part::GeomLineSegment::getClassTypeId()),
380
            std::forward_as_tuple(
381
                std::initializer_list<
382
                    std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
383
                    {Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Line_Edge")},
384
                    {Sketcher::PointPos::start, getMultIcon("Sketcher_Element_Line_StartingPoint")},
385
                    {Sketcher::PointPos::end, getMultIcon("Sketcher_Element_Line_EndPoint")},
386
                }));
387

388
        icons.emplace(
389
            std::piecewise_construct,
390
            std::forward_as_tuple(Part::GeomPoint::getClassTypeId()),
391
            std::forward_as_tuple(
392
                std::initializer_list<
393
                    std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
394
                    {Sketcher::PointPos::start,
395
                     getMultIcon("Sketcher_Element_Point_StartingPoint")},
396
                }));
397

398
        icons.emplace(
399
            std::piecewise_construct,
400
            std::forward_as_tuple(Part::GeomEllipse::getClassTypeId()),
401
            std::forward_as_tuple(
402
                std::initializer_list<
403
                    std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
404
                    {Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Ellipse_Edge_2")},
405
                    {Sketcher::PointPos::mid, getMultIcon("Sketcher_Element_Ellipse_CentrePoint")},
406
                }));
407

408
        icons.emplace(
409
            std::piecewise_construct,
410
            std::forward_as_tuple(Part::GeomArcOfEllipse::getClassTypeId()),
411
            std::forward_as_tuple(
412
                std::initializer_list<
413
                    std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
414
                    {Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Elliptical_Arc_Edge")},
415
                    {Sketcher::PointPos::start,
416
                     getMultIcon("Sketcher_Element_Elliptical_Arc_Start_Point")},
417
                    {Sketcher::PointPos::end,
418
                     getMultIcon("Sketcher_Element_Elliptical_Arc_End_Point")},
419
                    {Sketcher::PointPos::mid,
420
                     getMultIcon("Sketcher_Element_Elliptical_Arc_Centre_Point")},
421
                }));
422

423
        icons.emplace(
424
            std::piecewise_construct,
425
            std::forward_as_tuple(Part::GeomArcOfHyperbola::getClassTypeId()),
426
            std::forward_as_tuple(
427
                std::initializer_list<
428
                    std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
429
                    {Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Hyperbolic_Arc_Edge")},
430
                    {Sketcher::PointPos::start,
431
                     getMultIcon("Sketcher_Element_Hyperbolic_Arc_Start_Point")},
432
                    {Sketcher::PointPos::end,
433
                     getMultIcon("Sketcher_Element_Hyperbolic_Arc_End_Point")},
434
                    {Sketcher::PointPos::mid,
435
                     getMultIcon("Sketcher_Element_Hyperbolic_Arc_Centre_Point")},
436
                }));
437

438
        icons.emplace(
439
            std::piecewise_construct,
440
            std::forward_as_tuple(Part::GeomArcOfParabola::getClassTypeId()),
441
            std::forward_as_tuple(
442
                std::initializer_list<
443
                    std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
444
                    {Sketcher::PointPos::none, getMultIcon("Sketcher_Element_Parabolic_Arc_Edge")},
445
                    {Sketcher::PointPos::start,
446
                     getMultIcon("Sketcher_Element_Parabolic_Arc_Start_Point")},
447
                    {Sketcher::PointPos::end,
448
                     getMultIcon("Sketcher_Element_Parabolic_Arc_End_Point")},
449
                    {Sketcher::PointPos::mid,
450
                     getMultIcon("Sketcher_Element_Parabolic_Arc_Centre_Point")},
451
                }));
452

453
        icons.emplace(
454
            std::piecewise_construct,
455
            std::forward_as_tuple(Part::GeomBSplineCurve::getClassTypeId()),
456
            std::forward_as_tuple(
457
                std::initializer_list<
458
                    std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
459
                    {Sketcher::PointPos::none, getMultIcon("Sketcher_Element_BSpline_Edge")},
460
                    {Sketcher::PointPos::start, getMultIcon("Sketcher_Element_BSpline_StartPoint")},
461
                    {Sketcher::PointPos::end, getMultIcon("Sketcher_Element_BSpline_EndPoint")},
462
                }));
463

464
        icons.emplace(
465
            std::piecewise_construct,
466
            std::forward_as_tuple(Base::Type::badType()),
467
            std::forward_as_tuple(
468
                std::initializer_list<
469
                    std::pair<const Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>> {
470
                    {Sketcher::PointPos::none,
471
                     getMultIcon("Sketcher_Element_SelectionTypeInvalid")},
472
                }));
473
    }
474

475
    const QIcon& getIconImpl(Base::Type type, Sketcher::PointPos pos,
476
                             ElementItem::GeometryState icontype)
477
    {
478

479
        auto typekey = icons.find(type);
480

481
        if (typekey == icons.end()) {// Not supported Geometry Type - Defaults to invalid icon
482
            typekey = icons.find(Base::Type::badType());
483
            pos = Sketcher::PointPos::none;
484
        }
485

486
        auto poskey = typekey->second.find(pos);
487

488
        if (poskey == typekey->second.end()) {// invalid PointPos for type - Provide Invalid icon
489
            typekey = icons.find(Base::Type::badType());
490
            pos = Sketcher::PointPos::none;
491
            poskey = typekey->second.find(pos);
492
        }
493

494
        if (icontype == ElementItem::GeometryState::Normal)
495
            return std::get<0>(poskey->second);
496
        else if (icontype == ElementItem::GeometryState::Construction)
497
            return std::get<1>(poskey->second);
498
        else if (icontype == ElementItem::GeometryState::External)
499
            return std::get<2>(poskey->second);
500
        else// internal alignment
501
            return std::get<3>(poskey->second);
502

503
        // We should never arrive here, as badtype, PointPos::none must exist.
504
        throw Base::ValueError("Icon for Invalid is missing!!");
505
    }
506

507
    std::tuple<QIcon, QIcon, QIcon, QIcon> getMultIcon(const char* name)
508
    {
509
        int hue, sat, val, alp;
510
        QIcon Normal = Gui::BitmapFactory().iconFromTheme(name);
511
        QImage imgConstr(Normal.pixmap(std::as_const(Normal).availableSizes()[0]).toImage());
512
        QImage imgExt(imgConstr);
513
        QImage imgInt(imgConstr);
514

515
        // Create construction/external/internal icons by changing colors.
516
        for (int ix = 0; ix < imgConstr.width(); ix++) {
517
            for (int iy = 0; iy < imgConstr.height(); iy++) {
518
                QColor clr(imgConstr.pixelColor(ix, iy));
519
                clr.getHsv(&hue, &sat, &val, &alp);
520
                if (alp > 127 && hue >= 0) {
521
                    if (sat > 127 && (hue > 330 || hue < 30)) {// change the color of red points.
522
                        clr.setHsv((hue + 240) % 360, sat, val, alp);
523
                        imgConstr.setPixelColor(ix, iy, clr);
524
                        clr.setHsv((hue + 300) % 360, sat, val, alp);
525
                        imgExt.setPixelColor(ix, iy, clr);
526
                        clr.setHsv((hue + 60) % 360,
527
                                   (int)(sat / 3),
528
                                   std::min((int)(val * 8 / 7), 255),
529
                                   alp);
530
                        imgInt.setPixelColor(ix, iy, clr);
531
                    }
532
                    else if (sat < 64 && val > 192) {// change the color of white edges.
533
                        clr.setHsv(240, (255 - sat), val, alp);
534
                        imgConstr.setPixel(ix, iy, clr.rgba());
535
                        clr.setHsv(300, (255 - sat), val, alp);
536
                        imgExt.setPixel(ix, iy, clr.rgba());
537
                        clr.setHsv(60, (int)(255 - sat) / 2, val, alp);
538
                        imgInt.setPixel(ix, iy, clr.rgba());
539
                    }
540
                }
541
            }
542
        }
543
        QIcon Construction = QIcon(QPixmap::fromImage(imgConstr));
544
        QIcon External = QIcon(QPixmap::fromImage(imgExt));
545
        QIcon Internal = QIcon(QPixmap::fromImage(imgInt));
546

547
        return std::make_tuple(Normal, Construction, External, Internal);
548
    }
549

550
private:
551
    std::map<Base::Type, std::map<Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>>
552
        icons;
553
};
554

555
ElementView::ElementView(QWidget* parent)
556
    : QListWidget(parent)
557
{
558
    ElementItemDelegate* elementItemDelegate = new ElementItemDelegate(this);
559
    setItemDelegate(elementItemDelegate);
560

561
    QObject::connect(
562
        elementItemDelegate, &ElementItemDelegate::itemHovered, this, &ElementView::onIndexHovered);
563

564
    QObject::connect(
565
        elementItemDelegate, &ElementItemDelegate::itemChecked, this, &ElementView::onIndexChecked);
566
}
567

568
ElementView::~ElementView()
569
{}
570

571
void ElementView::changeLayer(int layer)
572
{
573
    App::Document* doc = App::GetApplication().getActiveDocument();
574

575
    if (!doc)
576
        return;
577

578
    doc->openTransaction("Geometry Layer Change");
579
    std::vector<Gui::SelectionObject> sel = Gui::Selection().getSelectionEx(doc->getName());
580
    for (std::vector<Gui::SelectionObject>::iterator ft = sel.begin(); ft != sel.end(); ++ft) {
581
        auto sketchobject = dynamic_cast<Sketcher::SketchObject*>(ft->getObject());
582

583
        auto geoids = getGeoIdsOfEdgesFromNames(sketchobject, ft->getSubNames());
584

585
        auto geometry = sketchobject->Geometry.getValues();
586
        auto newgeometry(geometry);
587

588
        bool anychanged = false;
589
        for (auto geoid : geoids) {
590
            if (geoid
591
                >= 0) {// currently only internal geometry can be changed from one layer to another
592
                auto currentlayer = getSafeGeomLayerId(geometry[geoid]);
593
                if (currentlayer != layer) {
594
                    auto geo = geometry[geoid]->clone();
595
                    setSafeGeomLayerId(geo, layer);
596
                    newgeometry[geoid] = geo;
597
                    anychanged = true;
598
                }
599
            }
600
            else {
601
                Gui::TranslatedUserWarning(
602
                    sketchobject,
603
                    QObject::tr("Unsupported visual layer operation"),
604
                    QObject::tr("It is currently unsupported to move external geometry to another "
605
                                "visual layer. External geometry will be omitted"));
606
            }
607
        }
608

609
        if (anychanged) {
610
            sketchobject->Geometry.setValues(std::move(newgeometry));
611
            sketchobject->solve();
612
        }
613
    }
614
    doc->commitTransaction();
615
}
616

617
void ElementView::changeLayer(ElementItem* item, int layer)
618
{
619
    App::Document* doc = App::GetApplication().getActiveDocument();
620

621
    if (!doc) {
622
        return;
623
    }
624

625
    doc->openTransaction("Geometry Layer Change");
626

627
    auto sketchObject = item->getSketchObject();
628

629
    auto geometry = sketchObject->Geometry.getValues();
630
    auto newGeometry(geometry);
631

632
    auto geoid = item->ElementNbr;
633

634
    // currently only internal geometry can be changed from one layer to another
635
    if (geoid >= 0) {
636
        auto currentLayer = getSafeGeomLayerId(geometry[geoid]);
637

638
        if (currentLayer != layer) {
639
            auto geo = geometry[geoid]->clone();
640
            setSafeGeomLayerId(geo, layer);
641
            newGeometry[geoid] = geo;
642

643
            sketchObject->Geometry.setValues(std::move(newGeometry));
644
            sketchObject->solve();
645
        }
646
    }
647
    else {
648
        Gui::TranslatedUserWarning(
649
            sketchObject,
650
            QObject::tr("Unsupported visual layer operation"),
651
            QObject::tr("It is currently unsupported to move external geometry to another "
652
                        "visual layer. External geometry will be omitted"));
653
    }
654

655
    doc->commitTransaction();
656
}
657

658
void ElementView::contextMenuEvent(QContextMenuEvent* event)
659
{
660
    QMenu menu;
661
    QList<QListWidgetItem*> items = selectedItems();
662

663
    // NOTE: If extending this context menu, be sure to add the items to the translation block at
664
    // the top of this file
665

666
    // CONTEXT_ITEM(ICONSTR,NAMESTR,CMDSTR,FUNC,ACTSONSELECTION)
667
    CONTEXT_ITEM("Constraint_PointOnPoint",
668
                 "Point Coincidence",
669
                 "Sketcher_ConstrainCoincident",
670
                 doPointCoincidence,
671
                 true)
672
    CONTEXT_ITEM("Constraint_PointOnObject",
673
                 "Point on Object",
674
                 "Sketcher_ConstrainPointOnObject",
675
                 doPointOnObjectConstraint,
676
                 true)
677
    CONTEXT_ITEM("Constraint_Horizontal",
678
                 "Horizontal Constraint",
679
                 "Sketcher_ConstrainHorizontal",
680
                 doHorizontalConstraint,
681
                 true)
682
    CONTEXT_ITEM("Constraint_Vertical",
683
                 "Vertical Constraint",
684
                 "Sketcher_ConstrainVertical",
685
                 doVerticalConstraint,
686
                 true)
687
    CONTEXT_ITEM("Constraint_Parallel",
688
                 "Parallel Constraint",
689
                 "Sketcher_ConstrainParallel",
690
                 doParallelConstraint,
691
                 true)
692
    CONTEXT_ITEM("Constraint_Perpendicular",
693
                 "Perpendicular Constraint",
694
                 "Sketcher_ConstrainPerpendicular",
695
                 doPerpendicularConstraint,
696
                 true)
697
    CONTEXT_ITEM("Constraint_Tangent",
698
                 "Tangent Constraint",
699
                 "Sketcher_ConstrainTangent",
700
                 doTangentConstraint,
701
                 true)
702
    CONTEXT_ITEM("Constraint_EqualLength",
703
                 "Equal Length",
704
                 "Sketcher_ConstrainEqual",
705
                 doEqualConstraint,
706
                 true)
707
    CONTEXT_ITEM("Constraint_Symmetric",
708
                 "Symmetric",
709
                 "Sketcher_ConstrainSymmetric",
710
                 doSymmetricConstraint,
711
                 true)
712
    CONTEXT_ITEM(
713
        "Constraint_Block", "Block Constraint", "Sketcher_ConstrainBlock", doBlockConstraint, true)
714

715
    CONTEXT_ITEM("Constraint_HorizontalDistance",
716
                 "Horizontal Distance",
717
                 "Sketcher_ConstrainDistanceX",
718
                 doHorizontalDistance,
719
                 true)
720
    CONTEXT_ITEM("Constraint_VerticalDistance",
721
                 "Vertical Distance",
722
                 "Sketcher_ConstrainDistanceY",
723
                 doVerticalDistance,
724
                 true)
725
    CONTEXT_ITEM("Constraint_Length",
726
                 "Length Constraint",
727
                 "Sketcher_ConstrainDistance",
728
                 doLengthConstraint,
729
                 true)
730
    CONTEXT_ITEM("Constraint_Radiam",
731
                 "Radiam Constraint",
732
                 "Sketcher_ConstrainRadiam",
733
                 doRadiamConstraint,
734
                 true)
735
    CONTEXT_ITEM("Constraint_Radius",
736
                 "Radius Constraint",
737
                 "Sketcher_ConstrainRadius",
738
                 doRadiusConstraint,
739
                 true)
740
    CONTEXT_ITEM("Constraint_Diameter",
741
                 "Diameter Constraint",
742
                 "Sketcher_ConstrainDiameter",
743
                 doDiameterConstraint,
744
                 true)
745
    CONTEXT_ITEM("Constraint_InternalAngle",
746
                 "Angle Constraint",
747
                 "Sketcher_ConstrainAngle",
748
                 doAngleConstraint,
749
                 true)
750
    CONTEXT_ITEM(
751
        "Constraint_Lock", "Lock Constraint", "Sketcher_ConstrainLock", doLockConstraint, true)
752

753
    menu.addSeparator();
754

755
    CONTEXT_ITEM("Sketcher_ToggleConstruction",
756
                 "Toggle construction geometry",
757
                 "Sketcher_ToggleConstruction",
758
                 doToggleConstruction,
759
                 true)
760

761
    menu.addSeparator();
762

763
    CONTEXT_ITEM("Sketcher_SelectConstraints",
764
                 "Select Constraints",
765
                 "Sketcher_SelectConstraints",
766
                 doSelectConstraints,
767
                 true)
768
    CONTEXT_ITEM(
769
        "Sketcher_SelectOrigin", "Select Origin", "Sketcher_SelectOrigin", doSelectOrigin, false)
770
    CONTEXT_ITEM("Sketcher_SelectHorizontalAxis",
771
                 "Select Horizontal Axis",
772
                 "Sketcher_SelectHorizontalAxis",
773
                 doSelectHAxis,
774
                 false)
775
    CONTEXT_ITEM("Sketcher_SelectVerticalAxis",
776
                 "Select Vertical Axis",
777
                 "Sketcher_SelectVerticalAxis",
778
                 doSelectVAxis,
779
                 false)
780

781
    menu.addSeparator();
782

783
    auto submenu = menu.addMenu(tr("Layer"));
784

785
    auto addLayerAction = [submenu, this, items](auto&& name, int layernumber) {
786
        auto action = submenu->addAction(std::forward<decltype(name)>(name), [this, layernumber]() {
787
            changeLayer(layernumber);
788
        });
789
        action->setEnabled(!items.isEmpty());
790
        return action;
791
    };
792

793
    addLayerAction(tr("Layer 0"), 0);
794
    addLayerAction(tr("Layer 1"), 1);
795
    addLayerAction(tr("Hidden"), 2);
796

797

798
    menu.addSeparator();
799

800
    QAction* remove = menu.addAction(tr("Delete"), this, &ElementView::deleteSelectedItems);
801
    remove->setShortcut(QKeySequence(QKeySequence::Delete));
802
    remove->setEnabled(!items.isEmpty());
803

804
    menu.menuAction()->setIconVisibleInMenu(true);
805

806
    menu.exec(event->globalPos());
807
}
808

809
CONTEXT_MEMBER_DEF("Sketcher_ConstrainCoincident", doPointCoincidence)
810
CONTEXT_MEMBER_DEF("Sketcher_ConstrainPointOnObject", doPointOnObjectConstraint)
811
CONTEXT_MEMBER_DEF("Sketcher_ConstrainHorizontal", doHorizontalConstraint)
812
CONTEXT_MEMBER_DEF("Sketcher_ConstrainVertical", doVerticalConstraint)
813
CONTEXT_MEMBER_DEF("Sketcher_ConstrainParallel", doParallelConstraint)
814
CONTEXT_MEMBER_DEF("Sketcher_ConstrainPerpendicular", doPerpendicularConstraint)
815
CONTEXT_MEMBER_DEF("Sketcher_ConstrainTangent", doTangentConstraint)
816
CONTEXT_MEMBER_DEF("Sketcher_ConstrainEqual", doEqualConstraint)
817
CONTEXT_MEMBER_DEF("Sketcher_ConstrainSymmetric", doSymmetricConstraint)
818
CONTEXT_MEMBER_DEF("Sketcher_ConstrainBlock", doBlockConstraint)
819

820
CONTEXT_MEMBER_DEF("Sketcher_ConstrainDistanceX", doHorizontalDistance)
821
CONTEXT_MEMBER_DEF("Sketcher_ConstrainDistanceY", doVerticalDistance)
822
CONTEXT_MEMBER_DEF("Sketcher_ConstrainDistance", doLengthConstraint)
823
CONTEXT_MEMBER_DEF("Sketcher_ConstrainRadiam", doRadiamConstraint)
824
CONTEXT_MEMBER_DEF("Sketcher_ConstrainRadius", doRadiusConstraint)
825
CONTEXT_MEMBER_DEF("Sketcher_ConstrainDiameter", doDiameterConstraint)
826
CONTEXT_MEMBER_DEF("Sketcher_ConstrainAngle", doAngleConstraint)
827
CONTEXT_MEMBER_DEF("Sketcher_ConstrainLock", doLockConstraint)
828

829
CONTEXT_MEMBER_DEF("Sketcher_ToggleConstruction", doToggleConstruction)
830

831
CONTEXT_MEMBER_DEF("Sketcher_SelectConstraints", doSelectConstraints)
832
CONTEXT_MEMBER_DEF("Sketcher_SelectOrigin", doSelectOrigin)
833
CONTEXT_MEMBER_DEF("Sketcher_SelectHorizontalAxis", doSelectHAxis)
834
CONTEXT_MEMBER_DEF("Sketcher_SelectVerticalAxis", doSelectVAxis)
835

836
void ElementView::deleteSelectedItems()
837
{
838
    App::Document* doc = App::GetApplication().getActiveDocument();
839
    if (!doc)
840
        return;
841

842
    doc->openTransaction("Delete element");
843
    std::vector<Gui::SelectionObject> sel = Gui::Selection().getSelectionEx(doc->getName());
844
    for (std::vector<Gui::SelectionObject>::iterator ft = sel.begin(); ft != sel.end(); ++ft) {
845
        Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(ft->getObject());
846
        if (vp) {
847
            vp->onDelete(ft->getSubNames());
848
        }
849
    }
850
    doc->commitTransaction();
851
}
852

853
void ElementView::onIndexHovered(QModelIndex index)
854
{
855
    update(index);
856

857
    Q_EMIT onItemHovered(itemFromIndex(index));
858
}
859

860
void ElementView::onIndexChecked(QModelIndex index, Qt::CheckState state)
861
{
862
    auto item = itemFromIndex(index);
863

864
    changeLayer(item, static_cast<int>(state == Qt::Checked ? ElementItem::Layer::Default : ElementItem::Layer::Hidden));
865
}
866

867
ElementItem* ElementView::itemFromIndex(const QModelIndex& index)
868
{
869
    return static_cast<ElementItem*>(QListWidget::itemFromIndex(index));
870
}
871

872
// clang-format on
873
/* ElementItem delegate ---------------------------------------------------- */
874
ElementItemDelegate::ElementItemDelegate(ElementView* parent)
875
    : QStyledItemDelegate(parent)
876
{  // This class relies on the parent being an ElementView, see getElementtItem
877
}
878

879
void ElementItemDelegate::paint(QPainter* painter,
880
                                const QStyleOptionViewItem& option,
881
                                const QModelIndex& index) const
882
{
883
    ElementItem* item = getElementItem(index);
884

885
    if (!item) {
886
        return;
887
    }
888

889
    auto style = option.widget ? option.widget->style() : QApplication::style();
890

891
    QStyleOptionViewItem itemOption = option;
892

893
    initStyleOption(&itemOption, index);
894

895
    if (item->isLineSelected || item->isStartingPointSelected || item->isEndPointSelected
896
        || item->isMidPointSelected) {
897
        itemOption.state |= QStyle::State_Active;
898
    }
899

900
    style->drawPrimitive(QStyle::PE_PanelItemViewItem, &itemOption, painter, option.widget);
901

902
    drawSubControl(SubControl::CheckBox, painter, option, index);
903
    drawSubControl(SubControl::LineSelect, painter, option, index);
904
    drawSubControl(SubControl::StartSelect, painter, option, index);
905
    drawSubControl(SubControl::EndSelect, painter, option, index);
906
    drawSubControl(SubControl::MidSelect, painter, option, index);
907
    drawSubControl(SubControl::Label, painter, option, index);
908
}
909

910
QRect ElementItemDelegate::subControlRect(SubControl element,
911
                                          const QStyleOptionViewItem& option,
912
                                          const QModelIndex& index) const
913
{
914
    auto itemOption = option;
915

916
    auto style = option.widget ? option.widget->style() : QApplication::style();
917

918
    initStyleOption(&itemOption, index);
919

920
    QRect checkBoxRect =
921
        style->subElementRect(QStyle::SE_CheckBoxIndicator, &itemOption, option.widget);
922

923
    checkBoxRect.moveTo(gap,
924
                        option.rect.top() + (option.rect.height() - checkBoxRect.height()) / 2);
925

926
    if (element == SubControl::CheckBox) {
927
        return checkBoxRect;
928
    }
929

930
    QRect selectRect =
931
        style->subElementRect(QStyle::SE_ItemViewItemDecoration, &itemOption, option.widget)
932
            .translated(checkBoxRect.right() + gap, 0);
933

934
    unsigned pos = element - SubControl::LineSelect;
935

936
    auto rect = selectRect.translated((selectRect.width() + gap) * pos, 0);
937

938
    if (element != SubControl::Label) {
939
        return rect;
940
    }
941

942
    rect.setRight(itemOption.rect.right());
943

944
    return rect;
945
}
946

947
void ElementItemDelegate::drawSubControl(SubControl element,
948
                                         QPainter* painter,
949
                                         const QStyleOptionViewItem& option,
950
                                         const QModelIndex& index) const
951
{
952
    auto item = getElementItem(index);
953
    auto style = option.widget ? option.widget->style() : QApplication::style();
954

955
    auto rect = subControlRect(element, option, index);
956

957
    auto mousePos = option.widget->mapFromGlobal(QCursor::pos());
958
    auto isHovered = rect.contains(mousePos);
959

960
    auto drawSelectIcon = [&](Sketcher::PointPos pos) {
961
        auto icon = ElementWidgetIcons::getIcon(item->GeometryType, pos, item->State);
962

963
        auto isOptionSelected = option.state & QStyle::State_Selected;
964
        auto isOptionHovered = option.state & QStyle::State_MouseOver;
965

966
        // items that user is not interacting with should be fully opaque
967
        // only if item is partially selected (so only one part of geometry)
968
        // the rest should be dimmed out
969
        auto opacity = isOptionHovered || isOptionSelected ? 0.4 : 1.0;
970

971
        if (item->isGeometryPreselected(pos)) {
972
            opacity = 0.8f;
973
        }
974

975
        if (item->isGeometrySelected(pos)) {
976
            opacity = 1.0f;
977
        }
978

979
        painter->setOpacity(opacity);
980
        painter->drawPixmap(rect, icon.pixmap(rect.size()));
981
    };
982

983
    painter->save();
984

985
    switch (element) {
986
        case SubControl::CheckBox: {
987
            QStyleOptionButton checkboxOption;
988

989
            checkboxOption.initFrom(option.widget);
990
            checkboxOption.rect = rect;
991

992
            checkboxOption.state.setFlag(QStyle::State_Enabled, item->canBeHidden());
993

994
            if (isHovered) {
995
                checkboxOption.state |= QStyle::State_MouseOver;
996
            }
997

998
            if (item->isVisible()) {
999
                checkboxOption.state |= QStyle::State_On;
1000
            }
1001
            else {
1002
                checkboxOption.state |= QStyle::State_Off;
1003
            }
1004

1005
            style->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck,
1006
                                 &checkboxOption,
1007
                                 painter,
1008
                                 option.widget);
1009

1010
            break;
1011
        }
1012

1013
        case LineSelect: {
1014
            drawSelectIcon(Sketcher::PointPos::none);
1015
            break;
1016
        }
1017

1018
        case StartSelect: {
1019
            drawSelectIcon(Sketcher::PointPos::start);
1020
            break;
1021
        }
1022

1023
        case EndSelect: {
1024
            drawSelectIcon(Sketcher::PointPos::end);
1025
            break;
1026
        }
1027

1028
        case MidSelect: {
1029
            drawSelectIcon(Sketcher::PointPos::mid);
1030
            break;
1031
        }
1032

1033
        case Label: {
1034
            QRect rect = subControlRect(SubControl::Label, option, index);
1035

1036
            auto labelBoundingBox = painter->fontMetrics().tightBoundingRect(item->label);
1037

1038
            painter->drawText(rect.x(),
1039
                              option.rect.bottom()
1040
                                  - (option.rect.height() - labelBoundingBox.height()) / 2,
1041
                              item->label);
1042

1043
            break;
1044
        }
1045
    }
1046

1047
    painter->restore();
1048
}
1049
// clang-format off
1050

1051
bool ElementItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model,
1052
                                      const QStyleOptionViewItem& option, const QModelIndex& index)
1053
{
1054
    auto item = getElementItem(index);
1055

1056
    auto getSubElementType = [&](QPoint pos) {
1057
        if (subControlRect(SubControl::LineSelect, option, index).contains(pos)) {
1058
            return SubElementType::edge;
1059
        } else if (subControlRect(SubControl::StartSelect, option, index).contains(pos)) {
1060
            return SubElementType::start;
1061
        } else if (subControlRect(SubControl::EndSelect, option, index).contains(pos)) {
1062
            return SubElementType::end;
1063
        } else if (subControlRect(SubControl::MidSelect, option, index).contains(pos)) {
1064
            return SubElementType::mid;
1065
        } else {
1066
            // depending on geometry type by default we select either point or edge
1067
            return item->GeometryType == Part::GeomPoint::getClassTypeId() ? SubElementType::start : SubElementType::edge;
1068
        }
1069
    };
1070

1071
    if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) {
1072
        auto mouseEvent = static_cast<QMouseEvent*>(event);
1073

1074
        item->clickedOn = getSubElementType(mouseEvent->pos());
1075
        item->rightClicked = mouseEvent->button() == Qt::RightButton;
1076

1077
        if (item->canBeHidden()) {
1078
            QRect checkboxRect = subControlRect(SubControl::CheckBox, option, index);
1079

1080
            if (mouseEvent->button() == Qt::LeftButton && checkboxRect.contains(mouseEvent->pos())) {
1081
                Q_EMIT itemChecked(index, item->isVisible() ? Qt::Unchecked : Qt::Checked);
1082
            }
1083
        }
1084
    }
1085
    else if (event->type() == QEvent::MouseMove) {
1086
        auto mouseEvent = static_cast<QMouseEvent*>(event);
1087

1088
        item->hovered = getSubElementType(mouseEvent->pos());
1089

1090
        Q_EMIT itemHovered(index);
1091
    }
1092

1093
    return QStyledItemDelegate::editorEvent(event, model, option, index);
1094
}
1095

1096
ElementItem* ElementItemDelegate::getElementItem(const QModelIndex& index) const
1097
{
1098
    ElementView* elementView = static_cast<ElementView*>(parent());
1099
    return elementView->itemFromIndex(index);
1100
}
1101

1102
/* Filter element list widget ------------------------------------------------------ */
1103

1104
enum class GeoFilterType
1105
{
1106
    NormalGeos,
1107
    ConstructionGeos,
1108
    InternalGeos,
1109
    ExternalGeos,
1110
    AllGeosTypes,
1111
    PointGeos,
1112
    LineGeos,
1113
    CircleGeos,
1114
    EllipseGeos,
1115
    ArcGeos,
1116
    ArcOfEllipseGeos,
1117
    HyperbolaGeos,
1118
    ParabolaGeos,
1119
    BSplineGeos
1120
};
1121

1122
ElementFilterList::ElementFilterList(QWidget* parent)
1123
    : QListWidget(parent)
1124
{
1125
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
1126
        "User parameter:BaseApp/Preferences/Mod/Sketcher/General");
1127
    int filterState = hGrp->GetInt("ElementFilterState",
1128
                                   INT_MAX);// INT_MAX = 1111111111111111111111111111111 in binary.
1129

1130
    for (auto const& filterItem : filterItems) {
1131
        Q_UNUSED(filterItem);
1132
        auto it = new QListWidgetItem();
1133
        it->setFlags(it->flags() | Qt::ItemIsUserCheckable);
1134

1135
        bool isChecked = static_cast<bool>(filterState & 1);// get the first bit of filterState
1136
        it->setCheckState(isChecked ? Qt::Checked : Qt::Unchecked);
1137
        filterState = filterState >> 1;// shift right to get rid of the used bit.
1138

1139
        addItem(it);
1140
    }
1141
    languageChange();
1142

1143
    // We need to fix the state of 'All' group checkbox in case it is partially checked.
1144
    int indexOfAllTypes = static_cast<int>(GeoFilterType::AllGeosTypes);
1145
    if (item(indexOfAllTypes)->checkState() == Qt::Unchecked) {
1146
        bool allUnchecked = true;
1147
        for (int i = indexOfAllTypes + 1; i < count(); i++) {
1148
            if (item(i)->checkState() == Qt::Checked) {
1149
                allUnchecked = false;
1150
                break;
1151
            }
1152
        }
1153
        if (!allUnchecked)
1154
            item(indexOfAllTypes)->setCheckState(Qt::PartiallyChecked);
1155
    }
1156
}
1157

1158
ElementFilterList::~ElementFilterList()
1159
{}
1160

1161
void ElementFilterList::changeEvent(QEvent* e)
1162
{
1163
    if (e->type() == QEvent::LanguageChange) {
1164
        languageChange();
1165
    }
1166
    QWidget::changeEvent(e);
1167
}
1168

1169
void ElementFilterList::languageChange()
1170
{
1171
    assert(static_cast<int>(filterItems.size()) == count());
1172
    int i = 0;
1173
    for (auto const& filterItem : filterItems) {
1174
        auto text = QStringLiteral("  ").repeated(filterItem.second - 1)
1175
            + (filterItem.second > 0 ? QStringLiteral("- ") : QStringLiteral(""))
1176
            + tr(filterItem.first);
1177
        item(i++)->setText(text);
1178
    }
1179
}
1180

1181

1182
/* TRANSLATOR SketcherGui::TaskSketcherElements */
1183

1184
TaskSketcherElements::TaskSketcherElements(ViewProviderSketch* sketchView)
1185
    : TaskBox(Gui::BitmapFactory().pixmap("Sketcher_CreateLine"), tr("Elements"), true, nullptr)
1186
    , sketchView(sketchView)
1187
    , ui(new Ui_TaskSketcherElements())
1188
    , focusItemIndex(-1)
1189
    , previouslySelectedItemIndex(-1)
1190
    , previouslyHoveredItemIndex(-1)
1191
    , previouslyHoveredType(SubElementType::none)
1192
    , isNamingBoxChecked(false)
1193
{
1194
    // we need a separate container widget to add all controls to
1195
    proxy = new QWidget(this);
1196
    ui->setupUi(proxy);
1197
#ifdef Q_OS_MAC
1198
    QString cmdKey = QString::fromUtf8("\xe2\x8c\x98");// U+2318
1199
#else
1200
    // translate the text (it's offered by Qt's translation files)
1201
    // but avoid being picked up by lupdate
1202
    const char* ctrlKey = "Ctrl";
1203
    QString cmdKey = QShortcut::tr(ctrlKey);
1204
#endif
1205
    Q_UNUSED(cmdKey)
1206

1207
    ui->listWidgetElements->setSelectionMode(QAbstractItemView::ExtendedSelection);
1208
    ui->listWidgetElements->setEditTriggers(QListWidget::NoEditTriggers);
1209
    ui->listWidgetElements->setMouseTracking(true);
1210

1211
    createFilterButtonActions();
1212
    createSettingsButtonActions();
1213

1214
    connectSignals();
1215

1216
    this->groupLayout()->addWidget(proxy);
1217

1218
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
1219
        "User parameter:BaseApp/Preferences/Mod/Sketcher/General");
1220
    ui->filterBox->setChecked(hGrp->GetBool("ElementFilterEnabled", true));
1221
    ui->filterButton->setEnabled(ui->filterBox->isChecked());
1222

1223
    slotElementsChanged();
1224
}
1225

1226
TaskSketcherElements::~TaskSketcherElements()
1227
{
1228
    connectionElementsChanged.disconnect();
1229
}
1230

1231
void TaskSketcherElements::connectSignals()
1232
{
1233
    // connecting the needed signals
1234
    QObject::connect(ui->listWidgetElements,
1235
                     &ElementView::itemPressed,
1236
                     this,
1237
                     &TaskSketcherElements::onListWidgetElementsItemPressed);
1238
    QObject::connect(ui->listWidgetElements,
1239
                     &ElementView::itemEntered,
1240
                     this,
1241
                     &TaskSketcherElements::onListWidgetElementsItemEntered);
1242
    QObject::connect(ui->listWidgetElements,
1243
                     &ElementView::onItemHovered,
1244
                     this,
1245
                     &TaskSketcherElements::onListWidgetElementsMouseMoveOnItem);
1246
    QObject::connect(filterList,
1247
                     &QListWidget::itemChanged,
1248
                     this,
1249
                     &TaskSketcherElements::onListMultiFilterItemChanged);
1250
    QObject::connect(ui->filterBox,
1251
                     &QCheckBox::stateChanged,
1252
                     this,
1253
                     &TaskSketcherElements::onFilterBoxStateChanged);
1254
    QObject::connect(
1255
        ui->settingsButton, &QToolButton::clicked, ui->settingsButton, &QToolButton::showMenu);
1256
    QObject::connect(std::as_const(ui->settingsButton)->actions()[0],
1257
                     &QAction::changed,
1258
                     this,
1259
                     &TaskSketcherElements::onSettingsExtendedInformationChanged);
1260
    QObject::connect(
1261
        ui->filterButton, &QToolButton::clicked, ui->filterButton, &QToolButton::showMenu);
1262

1263
    //NOLINTBEGIN
1264
    connectionElementsChanged = sketchView->signalElementsChanged.connect(
1265
        std::bind(&SketcherGui::TaskSketcherElements::slotElementsChanged, this));
1266
    //NOLINTEND
1267
}
1268

1269
/* filter functions --------------------------------------------------- */
1270

1271
void TaskSketcherElements::createFilterButtonActions()
1272
{
1273
    auto* action = new QWidgetAction(this);
1274
    filterList = new ElementFilterList(this);
1275
    action->setDefaultWidget(filterList);
1276
    std::as_const(ui->filterButton)->addAction(action);
1277
}
1278

1279
void TaskSketcherElements::onFilterBoxStateChanged(int val)
1280
{
1281
    Q_UNUSED(val);
1282
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
1283
        "User parameter:BaseApp/Preferences/Mod/Sketcher/General");
1284
    hGrp->SetBool("ElementFilterEnabled", ui->filterBox->checkState() == Qt::Checked);
1285

1286
    ui->filterButton->setEnabled(ui->filterBox->checkState() == Qt::Checked);
1287
    slotElementsChanged();
1288
}
1289

1290
void TaskSketcherElements::onListMultiFilterItemChanged(QListWidgetItem* item)
1291
{
1292
    {
1293
        QSignalBlocker sigblk(filterList);
1294

1295
        int index = filterList->row(item);
1296
        int indexOfAllTypes = static_cast<int>(GeoFilterType::AllGeosTypes);
1297

1298
        if (index == indexOfAllTypes) {
1299
            for (int i = indexOfAllTypes + 1; i < filterList->count(); i++) {
1300
                filterList->item(i)->setCheckState(item->checkState());
1301
            }
1302
        }
1303
        else if (index > indexOfAllTypes) {
1304
            bool atLeastOneUnchecked = false;
1305
            bool atLeastOneChecked = false;
1306

1307
            for (int i = indexOfAllTypes + 1; i < filterList->count(); i++) {
1308
                if (filterList->item(i)->checkState() == Qt::Checked)
1309
                    atLeastOneChecked = true;
1310
                if (filterList->item(i)->checkState() == Qt::Unchecked)
1311
                    atLeastOneUnchecked = true;
1312
            }
1313
            if (atLeastOneChecked && atLeastOneUnchecked)
1314
                filterList->item(indexOfAllTypes)->setCheckState(Qt::PartiallyChecked);
1315
            else if (atLeastOneUnchecked)
1316
                filterList->item(indexOfAllTypes)->setCheckState(Qt::Unchecked);
1317
            else if (atLeastOneChecked)
1318
                filterList->item(indexOfAllTypes)->setCheckState(Qt::Checked);
1319
        }
1320
    }
1321

1322
    // Save the state of the filter.
1323
    int filterState = INT_MIN;// INT_MIN = 000000000000000000000000000000 in binary.
1324
    for (int i = filterList->count() - 1; i >= 0; i--) {
1325
        bool isChecked = filterList->item(i)->checkState() == Qt::Checked;
1326
        filterState = filterState << 1;// we shift left first, else the list is shifted at the end.
1327
        filterState = filterState | (isChecked ? 1 : 0);
1328
    }
1329
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
1330
        "User parameter:BaseApp/Preferences/Mod/Sketcher/General");
1331
    hGrp->SetInt("ElementFilterState", filterState);
1332

1333
    updateVisibility();
1334
}
1335

1336
void TaskSketcherElements::setItemVisibility(QListWidgetItem* it)
1337
{
1338
    ElementItem* item = static_cast<ElementItem*>(it);
1339

1340
    if (ui->filterBox->checkState() == Qt::Unchecked) {
1341
        item->setHidden(false);
1342
        return;
1343
    }
1344

1345
    using GeometryState = ElementItem::GeometryState;
1346

1347
    if ((filterList->item(static_cast<int>(GeoFilterType::NormalGeos))->checkState()
1348
             == Qt::Unchecked
1349
         && item->State == GeometryState::Normal)
1350
        || (filterList->item(static_cast<int>(GeoFilterType::ConstructionGeos))->checkState()
1351
                == Qt::Unchecked
1352
            && item->State == GeometryState::Construction)
1353
        || (filterList->item(static_cast<int>(GeoFilterType::InternalGeos))->checkState()
1354
                == Qt::Unchecked
1355
            && item->State == GeometryState::InternalAlignment)
1356
        || (filterList->item(static_cast<int>(GeoFilterType::ExternalGeos))->checkState()
1357
                == Qt::Unchecked
1358
            && item->State == GeometryState::External)
1359
        || (filterList->item(static_cast<int>(GeoFilterType::PointGeos))->checkState()
1360
                == Qt::Unchecked
1361
            && item->GeometryType == Part::GeomPoint::getClassTypeId())
1362
        || (filterList->item(static_cast<int>(GeoFilterType::LineGeos))->checkState()
1363
                == Qt::Unchecked
1364
            && item->GeometryType == Part::GeomLineSegment::getClassTypeId())
1365
        || (filterList->item(static_cast<int>(GeoFilterType::CircleGeos))->checkState()
1366
                == Qt::Unchecked
1367
            && item->GeometryType == Part::GeomCircle::getClassTypeId())
1368
        || (filterList->item(static_cast<int>(GeoFilterType::EllipseGeos))->checkState()
1369
                == Qt::Unchecked
1370
            && item->GeometryType == Part::GeomEllipse::getClassTypeId())
1371
        || (filterList->item(static_cast<int>(GeoFilterType::ArcGeos))->checkState()
1372
                == Qt::Unchecked
1373
            && item->GeometryType == Part::GeomArcOfCircle::getClassTypeId())
1374
        || (filterList->item(static_cast<int>(GeoFilterType::ArcOfEllipseGeos))->checkState()
1375
                == Qt::Unchecked
1376
            && item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId())
1377
        || (filterList->item(static_cast<int>(GeoFilterType::HyperbolaGeos))->checkState()
1378
                == Qt::Unchecked
1379
            && item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId())
1380
        || (filterList->item(static_cast<int>(GeoFilterType::ParabolaGeos))->checkState()
1381
                == Qt::Unchecked
1382
            && item->GeometryType == Part::GeomArcOfParabola::getClassTypeId())
1383
        || (filterList->item(static_cast<int>(GeoFilterType::BSplineGeos))->checkState()
1384
                == Qt::Unchecked
1385
            && item->GeometryType == Part::GeomBSplineCurve::getClassTypeId())) {
1386
        item->setHidden(true);
1387
        return;
1388
    }
1389
    item->setHidden(false);
1390
}
1391

1392
void TaskSketcherElements::updateVisibility()
1393
{
1394
    for (int i = 0; i < ui->listWidgetElements->count(); i++) {
1395
        setItemVisibility(ui->listWidgetElements->item(i));
1396
    }
1397
}
1398

1399
/*------------------*/
1400
void TaskSketcherElements::onSelectionChanged(const Gui::SelectionChanges& msg)
1401
{
1402
    std::string temp;
1403
    if (msg.Type == Gui::SelectionChanges::ClrSelection) {
1404
        clearWidget();
1405
    }
1406
    else if (msg.Type == Gui::SelectionChanges::AddSelection
1407
             || msg.Type == Gui::SelectionChanges::RmvSelection) {
1408
        bool select = (msg.Type == Gui::SelectionChanges::AddSelection);
1409
        // is it this object??
1410
        if (strcmp(msg.pDocName, sketchView->getSketchObject()->getDocument()->getName()) == 0
1411
            && strcmp(msg.pObjectName, sketchView->getSketchObject()->getNameInDocument()) == 0) {
1412
            if (msg.pSubName) {
1413
                ElementItem* modified_item = NULL;
1414
                QString expr = QString::fromLatin1(msg.pSubName);
1415
                std::string shapetype(msg.pSubName);
1416
                // if-else edge vertex
1417
                if (shapetype.size() > 4 && shapetype.substr(0, 4) == "Edge") {
1418
                    QRegularExpression rx(QString::fromLatin1("^Edge(\\d+)$"));
1419
                    QRegularExpressionMatch match;
1420
                    boost::ignore_unused(expr.indexOf(rx, 0, &match));
1421
                    if (match.hasMatch()) {
1422
                        bool ok;
1423
                        int ElementId = match.captured(1).toInt(&ok) - 1;
1424
                        if (ok) {
1425
                            int countItems = ui->listWidgetElements->count();
1426
                            // TODO: This and the loop below get slow when we have a lot of items.
1427
                            // Perhaps we should also maintain a map so that we can look up items
1428
                            // by element number.
1429
                            for (int i = 0; i < countItems; i++) {
1430
                                ElementItem* item =
1431
                                    static_cast<ElementItem*>(ui->listWidgetElements->item(i));
1432
                                if (item->ElementNbr == ElementId) {
1433
                                    item->isLineSelected = select;
1434
                                    modified_item = item;
1435
                                    break;
1436
                                }
1437
                            }
1438
                        }
1439
                    }
1440
                }
1441
                else if (shapetype.size() > 12 && shapetype.substr(0, 12) == "ExternalEdge") {
1442
                    QRegularExpression rx(QString::fromLatin1("^ExternalEdge(\\d+)$"));
1443
                    QRegularExpressionMatch match;
1444
                    boost::ignore_unused(expr.indexOf(rx, 0, &match));
1445
                    if (match.hasMatch()) {
1446
                        bool ok;
1447
                        int ElementId = -match.captured(1).toInt(&ok) - 2;
1448
                        if (ok) {
1449
                            int countItems = ui->listWidgetElements->count();
1450
                            for (int i = 0; i < countItems; i++) {
1451
                                ElementItem* item =
1452
                                    static_cast<ElementItem*>(ui->listWidgetElements->item(i));
1453
                                if (item->ElementNbr == ElementId) {
1454
                                    item->isLineSelected = select;
1455
                                    modified_item = item;
1456
                                    break;
1457
                                }
1458
                            }
1459
                        }
1460
                    }
1461
                }
1462
                else if (shapetype.size() > 6 && shapetype.substr(0, 6) == "Vertex") {
1463
                    QRegularExpression rx(QString::fromLatin1("^Vertex(\\d+)$"));
1464
                    QRegularExpressionMatch match;
1465
                    boost::ignore_unused(expr.indexOf(rx, 0, &match));
1466
                    if (match.hasMatch()) {
1467
                        bool ok;
1468
                        int ElementId = match.captured(1).toInt(&ok) - 1;
1469
                        if (ok) {
1470
                            // Get the GeoID&Pos
1471
                            int GeoId;
1472
                            Sketcher::PointPos PosId;
1473
                            sketchView->getSketchObject()->getGeoVertexIndex(
1474
                                ElementId, GeoId, PosId);
1475

1476
                            int countItems = ui->listWidgetElements->count();
1477
                            for (int i = 0; i < countItems; i++) {
1478
                                ElementItem* item =
1479
                                    static_cast<ElementItem*>(ui->listWidgetElements->item(i));
1480
                                if (item->ElementNbr == GeoId) {
1481
                                    modified_item = item;
1482
                                    switch (PosId) {
1483
                                        case Sketcher::PointPos::start:
1484
                                            item->isStartingPointSelected = select;
1485
                                            break;
1486
                                        case Sketcher::PointPos::end:
1487
                                            item->isEndPointSelected = select;
1488
                                            break;
1489
                                        case Sketcher::PointPos::mid:
1490
                                            item->isMidPointSelected = select;
1491
                                            break;
1492
                                        default:
1493
                                            break;
1494
                                    }
1495
                                    break;
1496
                                }
1497
                            }
1498
                        }
1499
                    }
1500
                }
1501
                // update the listwidget
1502
                {
1503
                    QSignalBlocker sigblk(ui->listWidgetElements);
1504
                    if (modified_item != NULL) {
1505
                        bool is_selected = modified_item->isSelected();
1506
                        const bool should_be_selected = modified_item->isLineSelected
1507
                            || modified_item->isStartingPointSelected || modified_item->isEndPointSelected
1508
                            || modified_item->isMidPointSelected;
1509

1510
                        // If an element is already selected and a new subelement gets selected
1511
                        // (eg., if you select the arc of a circle then select the center as well),
1512
                        // the new subelement won't get highlighted in the list until you mouseover
1513
                        // the list.  To avoid this, we deselect first to trigger a redraw.
1514
                        if (should_be_selected && is_selected) {
1515
                            modified_item->setSelected(false);
1516
                            is_selected = false;
1517
                        }
1518

1519
                        if (should_be_selected != is_selected) {
1520
                          modified_item->setSelected(should_be_selected);
1521
                        }
1522
                    }
1523
                }
1524
            }
1525
        }
1526
    }
1527
    else if (msg.Type == Gui::SelectionChanges::SetSelection) {
1528
        // do nothing here
1529
    }
1530
}
1531

1532
void TaskSketcherElements::onListWidgetElementsItemPressed(QListWidgetItem* it)
1533
{
1534
    // We use itemPressed instead of previously used ItemSelectionChanged because if user click on
1535
    // already selected item, ItemSelectionChanged didn't trigger.
1536
    if (!it)
1537
        return;
1538

1539
    ElementItem* itf = static_cast<ElementItem*>(it);
1540
    bool rightClickOnSelected = itf->rightClicked
1541
        && (itf->isLineSelected || itf->isStartingPointSelected || itf->isEndPointSelected
1542
            || itf->isMidPointSelected);
1543
    itf->rightClicked = false;
1544
    if (rightClickOnSelected)// if user right clicked on a selected item, change nothing.
1545
        return;
1546

1547
    {
1548
        QSignalBlocker sigblk(ui->listWidgetElements);
1549

1550
        bool multipleselection = false;
1551
        bool multipleconsecutiveselection = false;
1552
        if (QApplication::keyboardModifiers() == Qt::ControlModifier)
1553
            multipleselection = true;
1554
        if (QApplication::keyboardModifiers() == Qt::ShiftModifier)
1555
            multipleconsecutiveselection = true;
1556

1557
        if (multipleselection
1558
            && multipleconsecutiveselection) {// ctrl takes priority over shift functionality
1559
            multipleselection = true;
1560
            multipleconsecutiveselection = false;
1561
        }
1562

1563
        std::vector<std::string> elementSubNames;
1564
        std::string doc_name = sketchView->getSketchObject()->getDocument()->getName();
1565
        std::string obj_name = sketchView->getSketchObject()->getNameInDocument();
1566

1567
        bool block = this->blockSelection(true);// avoid to be notified by itself
1568
        Gui::Selection().clearSelection();
1569

1570
        for (int i = 0; i < ui->listWidgetElements->count(); i++) {
1571
            ElementItem* item = static_cast<ElementItem*>(ui->listWidgetElements->item(i));
1572

1573
            if (!multipleselection && !multipleconsecutiveselection) {
1574
                // if not multiple selection, then all are disabled but the one that was just
1575
                // selected
1576
                item->isLineSelected = false;
1577
                item->isStartingPointSelected = false;
1578
                item->isEndPointSelected = false;
1579
                item->isMidPointSelected = false;
1580
            }
1581

1582
            if (item == itf) {
1583

1584
                if (item->clickedOn == SubElementType::mid
1585
                    && (item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
1586
                        || item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
1587
                        || item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
1588
                        || item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
1589
                        || item->GeometryType == Part::GeomCircle::getClassTypeId()
1590
                        || item->GeometryType == Part::GeomEllipse::getClassTypeId())) {
1591
                    item->isMidPointSelected = !item->isMidPointSelected;
1592
                }
1593
                else if (item->clickedOn == SubElementType::start
1594
                         && (item->GeometryType == Part::GeomPoint::getClassTypeId()
1595
                             || item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
1596
                             || item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
1597
                             || item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
1598
                             || item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
1599
                             || item->GeometryType == Part::GeomLineSegment::getClassTypeId()
1600
                             || item->GeometryType == Part::GeomBSplineCurve::getClassTypeId())) {
1601
                    item->isStartingPointSelected = !item->isStartingPointSelected;
1602
                }
1603
                else if (item->clickedOn == SubElementType::end
1604
                         && (item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
1605
                             || item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
1606
                             || item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
1607
                             || item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
1608
                             || item->GeometryType == Part::GeomLineSegment::getClassTypeId()
1609
                             || item->GeometryType == Part::GeomBSplineCurve::getClassTypeId())) {
1610
                    item->isEndPointSelected = !item->isEndPointSelected;
1611
                }
1612
                else if (item->clickedOn == SubElementType::edge
1613
                         && item->GeometryType != Part::GeomPoint::getClassTypeId()) {
1614
                    item->isLineSelected = !item->isLineSelected;
1615
                }
1616
                item->clickedOn = SubElementType::none;
1617
            }
1618
            else if (multipleconsecutiveselection && previouslySelectedItemIndex >= 0
1619
                     && !rightClickOnSelected
1620
                     && ((i > focusItemIndex && i < previouslySelectedItemIndex)
1621
                         || (i < focusItemIndex && i > previouslySelectedItemIndex))) {
1622
                if (item->GeometryType == Part::GeomPoint::getClassTypeId()) {
1623
                    item->isStartingPointSelected = true;
1624
                }
1625
                else {
1626
                    item->isLineSelected = true;
1627
                }
1628
            }
1629

1630
            // first update the listwidget. Item is selected if at least one element of the geo is
1631
            // selected.
1632
            bool selected = item->isLineSelected || item->isStartingPointSelected
1633
                || item->isEndPointSelected || item->isMidPointSelected;
1634

1635
            {
1636
                QSignalBlocker sigblk(ui->listWidgetElements);
1637

1638
                if (item->isSelected() && selected) {
1639
                    item->setSelected(
1640
                        false);// if already selected and changing or adding subelement, ensure
1641
                               // selection change is triggered, which ensures timely repaint
1642
                    item->setSelected(selected);
1643
                }
1644
                else {
1645
                    item->setSelected(selected);
1646
                }
1647
            }
1648

1649
            // now the scene
1650
            std::stringstream ss;
1651

1652

1653
            if (item->isLineSelected) {
1654
                if (item->ElementNbr >= 0) {
1655
                    ss << "Edge" << item->ElementNbr + 1;
1656
                }
1657
                else {
1658
                    ss << "ExternalEdge" << -item->ElementNbr - 2;
1659
                }
1660
                elementSubNames.push_back(ss.str());
1661
            }
1662

1663
            auto selectVertex = [&ss, &elementSubNames](bool subelementselected, int vertexid) {
1664
                if (subelementselected) {
1665
                    int vertex;
1666
                    ss.str(std::string());
1667
                    vertex = vertexid;
1668
                    if (vertex != -1) {
1669
                        ss << "Vertex" << vertex + 1;
1670
                        elementSubNames.push_back(ss.str());
1671
                    }
1672
                }
1673
            };
1674

1675
            selectVertex(item->isStartingPointSelected, item->StartingVertex);
1676
            selectVertex(item->isEndPointSelected, item->EndVertex);
1677
            selectVertex(item->isMidPointSelected, item->MidVertex);
1678
        }
1679

1680
        for (const auto& elementSubName : elementSubNames) {
1681
            Gui::Selection().addSelection2(
1682
                doc_name.c_str(),
1683
                obj_name.c_str(),
1684
                sketchView->getSketchObject()->convertSubName(elementSubName).c_str());
1685
        }
1686

1687
        this->blockSelection(block);
1688
    }
1689

1690
    if (focusItemIndex > -1 && focusItemIndex < ui->listWidgetElements->count())
1691
        previouslySelectedItemIndex = focusItemIndex;
1692

1693
    ui->listWidgetElements->repaint();
1694
}
1695

1696
void TaskSketcherElements::onListWidgetElementsItemEntered(QListWidgetItem* item)
1697
{
1698
    ui->listWidgetElements->setFocus();
1699

1700
    focusItemIndex = ui->listWidgetElements->row(item);
1701
}
1702

1703
void TaskSketcherElements::onListWidgetElementsMouseMoveOnItem(QListWidgetItem* it)
1704
{
1705
    ElementItem* item = static_cast<ElementItem*>(it);
1706

1707
    if (!item
1708
        || (ui->listWidgetElements->row(item) == previouslyHoveredItemIndex
1709
            && item->hovered == previouslyHoveredType))
1710
        return;
1711

1712
    Gui::Selection().rmvPreselect();
1713

1714
    bool validmid = item->hovered == SubElementType::mid
1715
        && (item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
1716
            || item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
1717
            || item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
1718
            || item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
1719
            || item->GeometryType == Part::GeomCircle::getClassTypeId()
1720
            || item->GeometryType == Part::GeomEllipse::getClassTypeId());
1721

1722
    bool validstartpoint = item->hovered == SubElementType::start
1723
        && (item->GeometryType == Part::GeomPoint::getClassTypeId()
1724
            || item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
1725
            || item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
1726
            || item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
1727
            || item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
1728
            || item->GeometryType == Part::GeomLineSegment::getClassTypeId()
1729
            || item->GeometryType == Part::GeomBSplineCurve::getClassTypeId());
1730

1731
    bool validendpoint = item->hovered == SubElementType::end
1732
        && (item->GeometryType == Part::GeomArcOfCircle::getClassTypeId()
1733
            || item->GeometryType == Part::GeomArcOfEllipse::getClassTypeId()
1734
            || item->GeometryType == Part::GeomArcOfHyperbola::getClassTypeId()
1735
            || item->GeometryType == Part::GeomArcOfParabola::getClassTypeId()
1736
            || item->GeometryType == Part::GeomLineSegment::getClassTypeId()
1737
            || item->GeometryType == Part::GeomBSplineCurve::getClassTypeId());
1738

1739
    bool validedge = item->hovered == SubElementType::edge
1740
        && item->GeometryType != Part::GeomPoint::getClassTypeId();
1741

1742
    if (validmid || validstartpoint || validendpoint || validedge) {
1743
        std::string doc_name = sketchView->getSketchObject()->getDocument()->getName();
1744
        std::string obj_name = sketchView->getSketchObject()->getNameInDocument();
1745

1746
        std::stringstream ss;
1747

1748
        auto preselectvertex = [&](int geoid, Sketcher::PointPos pos) {
1749
            int vertex = sketchView->getSketchObject()->getVertexIndexGeoPos(geoid, pos);
1750
            if (vertex != -1) {
1751
                ss << "Vertex" << vertex + 1;
1752
                Gui::Selection().setPreselect(doc_name.c_str(), obj_name.c_str(), ss.str().c_str());
1753
            }
1754
        };
1755

1756
        if (item->hovered == SubElementType::start)
1757
            preselectvertex(item->ElementNbr, Sketcher::PointPos::start);
1758
        else if (item->hovered == SubElementType::end)
1759
            preselectvertex(item->ElementNbr, Sketcher::PointPos::end);
1760
        else if (item->hovered == SubElementType::mid)
1761
            preselectvertex(item->ElementNbr, Sketcher::PointPos::mid);
1762
        else if (item->hovered == SubElementType::edge) {
1763
            if (item->ElementNbr >= 0) {
1764
                ss << "Edge" << item->ElementNbr + 1;
1765
            }
1766
            else {
1767
                ss << "ExternalEdge" << -item->ElementNbr - 2;
1768
            }
1769
            Gui::Selection().setPreselect(doc_name.c_str(), obj_name.c_str(), ss.str().c_str());
1770
        }
1771
    }
1772

1773
    previouslyHoveredItemIndex = ui->listWidgetElements->row(item);
1774
    previouslyHoveredType = item->hovered;
1775
}
1776

1777
void TaskSketcherElements::leaveEvent(QEvent* event)
1778
{
1779
    Q_UNUSED(event);
1780
    Gui::Selection().rmvPreselect();
1781
    ui->listWidgetElements->clearFocus();
1782
}
1783

1784
void TaskSketcherElements::slotElementsChanged()
1785
{
1786
    assert(sketchView);
1787
    // Build up ListView with the elements
1788
    Sketcher::SketchObject* sketch = sketchView->getSketchObject();
1789
    const std::vector<Part::Geometry*>& vals = sketch->Geometry.getValues();
1790

1791
    ui->listWidgetElements->clear();
1792

1793
    using GeometryState = ElementItem::GeometryState;
1794

1795
    int i = 1;
1796
    for (std::vector<Part::Geometry*>::const_iterator it = vals.begin(); it != vals.end();
1797
         ++it, ++i) {
1798
        Base::Type type = (*it)->getTypeId();
1799
        GeometryState state = GeometryState::Normal;
1800

1801
        bool construction = Sketcher::GeometryFacade::getConstruction(*it);
1802
        bool internalAligned = Sketcher::GeometryFacade::isInternalAligned(*it);
1803

1804
        auto layerId = getSafeGeomLayerId(*it);
1805

1806
        if (internalAligned)
1807
            state = GeometryState::InternalAlignment;
1808
        else if (construction)// Caution, internalAligned geos are construction too. So the 'if' and
1809
                              // 'else if' cannot be swapped.
1810
            state = GeometryState::Construction;
1811

1812
        auto IdInformation = [this, i, layerId]() {
1813
            if (sketchView->VisualLayerList.getSize() > 1)
1814
                return QString::fromLatin1("(Edge%1#ID%2#VL%3)").arg(i).arg(i - 1).arg(layerId);
1815
            else
1816
                return QString::fromLatin1("(Edge%1#ID%2)").arg(i).arg(i - 1);
1817
        };
1818

1819
        ElementItem* itemN = new ElementItem(
1820
            i - 1,
1821
            sketchView->getSketchObject()->getVertexIndexGeoPos(i - 1, Sketcher::PointPos::start),
1822
            sketchView->getSketchObject()->getVertexIndexGeoPos(i - 1, Sketcher::PointPos::mid),
1823
            sketchView->getSketchObject()->getVertexIndexGeoPos(i - 1, Sketcher::PointPos::end),
1824
            type,
1825
            state,
1826
            type == Part::GeomPoint::getClassTypeId()
1827
                ? (isNamingBoxChecked ? (tr("Point") + IdInformation())
1828
                           + (construction
1829
                                  ? (QString::fromLatin1("-") + tr("Construction"))
1830
                                  : (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
1831
                                                     : QString::fromLatin1("")))
1832
                                      : (QString::fromLatin1("%1-").arg(i) + tr("Point")))
1833
                : type == Part::GeomLineSegment::getClassTypeId()
1834
                ? (isNamingBoxChecked ? (tr("Line") + IdInformation())
1835
                           + (construction
1836
                                  ? (QString::fromLatin1("-") + tr("Construction"))
1837
                                  : (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
1838
                                                     : QString::fromLatin1("")))
1839
                                      : (QString::fromLatin1("%1-").arg(i) + tr("Line")))
1840
                : type == Part::GeomArcOfCircle::getClassTypeId()
1841
                ? (isNamingBoxChecked ? (tr("Arc") + IdInformation())
1842
                           + (construction
1843
                                  ? (QString::fromLatin1("-") + tr("Construction"))
1844
                                  : (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
1845
                                                     : QString::fromLatin1("")))
1846
                                      : (QString::fromLatin1("%1-").arg(i) + tr("Arc")))
1847
                : type == Part::GeomCircle::getClassTypeId()
1848
                ? (isNamingBoxChecked ? (tr("Circle") + IdInformation())
1849
                           + (construction
1850
                                  ? (QString::fromLatin1("-") + tr("Construction"))
1851
                                  : (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
1852
                                                     : QString::fromLatin1("")))
1853
                                      : (QString::fromLatin1("%1-").arg(i) + tr("Circle")))
1854
                : type == Part::GeomEllipse::getClassTypeId()
1855
                ? (isNamingBoxChecked ? (tr("Ellipse") + IdInformation())
1856
                           + (construction
1857
                                  ? (QString::fromLatin1("-") + tr("Construction"))
1858
                                  : (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
1859
                                                     : QString::fromLatin1("")))
1860
                                      : (QString::fromLatin1("%1-").arg(i) + tr("Ellipse")))
1861
                : type == Part::GeomArcOfEllipse::getClassTypeId()
1862
                ? (isNamingBoxChecked ? (tr("Elliptical Arc") + IdInformation())
1863
                           + (construction
1864
                                  ? (QString::fromLatin1("-") + tr("Construction"))
1865
                                  : (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
1866
                                                     : QString::fromLatin1("")))
1867
                                      : (QString::fromLatin1("%1-").arg(i) + tr("Elliptical Arc")))
1868
                : type == Part::GeomArcOfHyperbola::getClassTypeId()
1869
                ? (isNamingBoxChecked ? (tr("Hyperbolic Arc") + IdInformation())
1870
                           + (construction
1871
                                  ? (QString::fromLatin1("-") + tr("Construction"))
1872
                                  : (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
1873
                                                     : QString::fromLatin1("")))
1874
                                      : (QString::fromLatin1("%1-").arg(i) + tr("Hyperbolic Arc")))
1875
                : type == Part::GeomArcOfParabola::getClassTypeId()
1876
                ? (isNamingBoxChecked ? (tr("Parabolic Arc") + IdInformation())
1877
                           + (construction
1878
                                  ? (QString::fromLatin1("-") + tr("Construction"))
1879
                                  : (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
1880
                                                     : QString::fromLatin1("")))
1881
                                      : (QString::fromLatin1("%1-").arg(i) + tr("Parabolic Arc")))
1882
                : type == Part::GeomBSplineCurve::getClassTypeId()
1883
                ? (isNamingBoxChecked ? (tr("B-spline") + IdInformation())
1884
                           + (construction
1885
                                  ? (QString::fromLatin1("-") + tr("Construction"))
1886
                                  : (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
1887
                                                     : QString::fromLatin1("")))
1888
                                      : (QString::fromLatin1("%1-").arg(i) + tr("B-spline")))
1889
                : (isNamingBoxChecked ? (tr("Other") + IdInformation())
1890
                           + (construction
1891
                                  ? (QString::fromLatin1("-") + tr("Construction"))
1892
                                  : (internalAligned ? (QString::fromLatin1("-") + tr("Internal"))
1893
                                                     : QString::fromLatin1("")))
1894
                                      : (QString::fromLatin1("%1-").arg(i) + tr("Other"))),
1895
            sketchView);
1896

1897
        ui->listWidgetElements->addItem(itemN);
1898

1899
        setItemVisibility(itemN);
1900
    }
1901

1902
    const std::vector<Part::Geometry*>& ext_vals =
1903
        sketchView->getSketchObject()->getExternalGeometry();
1904

1905
    const std::vector<App::DocumentObject*> linkobjs =
1906
        sketchView->getSketchObject()->ExternalGeometry.getValues();
1907
    const std::vector<std::string> linksubs =
1908
        sketchView->getSketchObject()->ExternalGeometry.getSubValues();
1909

1910
    int j = 1;
1911
    for (std::vector<Part::Geometry*>::const_iterator it = ext_vals.begin(); it != ext_vals.end();
1912
         ++it, ++i, ++j) {
1913
        Base::Type type = (*it)->getTypeId();
1914

1915
        if (j > 2) {// we do not want the H and V axes
1916

1917
            auto layerId = getSafeGeomLayerId(*it);
1918

1919
            auto IdInformation = [this, j, layerId](bool link) {
1920
                if (sketchView->VisualLayerList.getSize() > 1) {
1921
                    if (link) {
1922
                        return QString::fromLatin1("(ExternalEdge%1#ID%2#VL%3, ")
1923
                            .arg(j - 2)
1924
                            .arg(-j)
1925
                            .arg(layerId);
1926
                    }
1927
                    else {
1928
                        return QString::fromLatin1("(ExternalEdge%1#ID%2#VL%3)")
1929
                            .arg(j - 2)
1930
                            .arg(-j)
1931
                            .arg(layerId);
1932
                    }
1933
                }
1934
                else {
1935
                    if (link) {
1936
                        return QString::fromLatin1("(ExternalEdge%1#ID%2, ").arg(j - 2).arg(-j);
1937
                    }
1938
                    else {
1939
                        return QString::fromLatin1("(ExternalEdge%1#ID%2)").arg(j - 2).arg(-j);
1940
                    }
1941
                }
1942
            };
1943

1944
            QString linkname;
1945

1946
            if (isNamingBoxChecked) {
1947
                if (size_t(j - 3) < linkobjs.size() && size_t(j - 3) < linksubs.size()) {
1948
                    linkname = IdInformation(true)
1949
                        + QString::fromUtf8(linkobjs[j - 3]->getNameInDocument())
1950
                        + QString::fromLatin1(".") + QString::fromUtf8(linksubs[j - 3].c_str())
1951
                        + QString::fromLatin1(")");
1952
                }
1953
                else {
1954
                    linkname = IdInformation(false);
1955
                }
1956
            }
1957

1958
            GeometryState state = GeometryState::External;
1959

1960
            ElementItem* itemN = new ElementItem(
1961
                -j,
1962
                sketchView->getSketchObject()->getVertexIndexGeoPos(-j, Sketcher::PointPos::start),
1963
                sketchView->getSketchObject()->getVertexIndexGeoPos(-j, Sketcher::PointPos::mid),
1964
                sketchView->getSketchObject()->getVertexIndexGeoPos(-j, Sketcher::PointPos::end),
1965
                type,
1966
                state,
1967
                type == Part::GeomPoint::getClassTypeId()
1968
                    ? (isNamingBoxChecked ? (tr("Point") + linkname)
1969
                                          : (QString::fromLatin1("%1-").arg(i - 2) + tr("Point")))
1970
                    : type == Part::GeomLineSegment::getClassTypeId()
1971
                    ? (isNamingBoxChecked ? (tr("Line") + linkname)
1972
                                          : (QString::fromLatin1("%1-").arg(i - 2) + tr("Line")))
1973
                    : type == Part::GeomArcOfCircle::getClassTypeId()
1974
                    ? (isNamingBoxChecked ? (tr("Arc") + linkname)
1975
                                          : (QString::fromLatin1("%1-").arg(i - 2) + tr("Arc")))
1976
                    : type == Part::GeomCircle::getClassTypeId()
1977
                    ? (isNamingBoxChecked ? (tr("Circle") + linkname)
1978
                                          : (QString::fromLatin1("%1-").arg(i - 2) + tr("Circle")))
1979
                    : type == Part::GeomEllipse::getClassTypeId()
1980
                    ? (isNamingBoxChecked ? (tr("Ellipse") + linkname)
1981
                                          : (QString::fromLatin1("%1-").arg(i - 2) + tr("Ellipse")))
1982
                    : type == Part::GeomArcOfEllipse::getClassTypeId()
1983
                    ? (isNamingBoxChecked
1984
                           ? (tr("Elliptical Arc") + linkname)
1985
                           : (QString::fromLatin1("%1-").arg(i - 2) + tr("Elliptical Arc")))
1986
                    : type == Part::GeomArcOfHyperbola::getClassTypeId()
1987
                    ? (isNamingBoxChecked
1988
                           ? (tr("Hyperbolic Arc") + linkname)
1989
                           : (QString::fromLatin1("%1-").arg(i - 2) + tr("Hyperbolic Arc")))
1990
                    : type == Part::GeomArcOfParabola::getClassTypeId()
1991
                    ? (isNamingBoxChecked
1992
                           ? (tr("Parabolic Arc") + linkname)
1993
                           : (QString::fromLatin1("%1-").arg(i - 2) + tr("Parabolic Arc")))
1994
                    : type == Part::GeomBSplineCurve::getClassTypeId()
1995
                    ? (isNamingBoxChecked ? (tr("B-spline") + linkname)
1996
                                          : (QString::fromLatin1("%1-").arg(i - 2) + tr("B-spline")))
1997
                    : (isNamingBoxChecked ? (tr("Other") + linkname)
1998
                                          : (QString::fromLatin1("%1-").arg(i - 2) + tr("Other"))),
1999
                sketchView);
2000

2001
            ui->listWidgetElements->addItem(itemN);
2002

2003
            setItemVisibility(itemN);
2004
        }
2005
    }
2006
}
2007

2008
void TaskSketcherElements::clearWidget()
2009
{
2010
    {
2011
        QSignalBlocker sigblk(ui->listWidgetElements);
2012
        ui->listWidgetElements->clearSelection();
2013
    }
2014

2015
    // update widget
2016
    int countItems = ui->listWidgetElements->count();
2017
    for (int i = 0; i < countItems; i++) {
2018
        ElementItem* item = static_cast<ElementItem*>(ui->listWidgetElements->item(i));
2019

2020
        item->isLineSelected = false;
2021
        item->isStartingPointSelected = false;
2022
        item->isEndPointSelected = false;
2023
        item->isMidPointSelected = false;
2024
    }
2025
}
2026

2027
void TaskSketcherElements::changeEvent(QEvent* e)
2028
{
2029
    TaskBox::changeEvent(e);
2030
    if (e->type() == QEvent::LanguageChange) {
2031
        ui->retranslateUi(proxy);
2032
    }
2033
}
2034

2035
/* Settings menu ==================================================*/
2036
void TaskSketcherElements::createSettingsButtonActions()
2037
{
2038
    QAction* action = new QAction(tr("Extended information"), this);
2039

2040
    action->setCheckable(true);
2041

2042
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
2043
        "User parameter:BaseApp/Preferences/Mod/Sketcher/Elements");
2044
    {
2045
        QSignalBlocker block(this);
2046
        action->setChecked(hGrp->GetBool("ExtendedNaming", false));
2047
    }
2048

2049
    ui->settingsButton->addAction(action);
2050

2051
    isNamingBoxChecked = hGrp->GetBool("ExtendedNaming", false);
2052
}
2053

2054
void TaskSketcherElements::onSettingsExtendedInformationChanged()
2055
{
2056
    QList<QAction*> acts = ui->settingsButton->actions();
2057
    isNamingBoxChecked = acts[0]->isChecked();
2058

2059
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
2060
        "User parameter:BaseApp/Preferences/Mod/Sketcher/Elements");
2061
    hGrp->SetBool("ExtendedNaming", isNamingBoxChecked);
2062

2063
    slotElementsChanged();
2064
}
2065

2066
#include "TaskSketcherElements.moc"// For Delegate as it is QOBJECT
2067
#include "moc_TaskSketcherElements.cpp"
2068
// clang-format on
2069

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.