FreeCAD

Форк
0
/
PropertyEditor.cpp 
997 строк · 34.1 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net>     *
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

24
#include "PreCompiled.h"
25

26
#ifndef _PreComp_
27
#include <boost/algorithm/string/predicate.hpp>
28
#include <QApplication>
29
#include <QInputDialog>
30
#include <QHeaderView>
31
#include <QMenu>
32
#include <QPainter>
33
#endif
34

35
#include <App/Application.h>
36
#include <App/AutoTransaction.h>
37
#include <App/Document.h>
38
#include <Base/Console.h>
39
#include <Base/Tools.h>
40

41
#include "PropertyEditor.h"
42
#include "DlgAddProperty.h"
43
#include "MainWindow.h"
44
#include "PropertyItemDelegate.h"
45
#include "PropertyModel.h"
46
#include "PropertyView.h"
47
#include "ViewProviderDocumentObject.h"
48

49

50
FC_LOG_LEVEL_INIT("PropertyView", true, true)
51

52
using namespace Gui::PropertyEditor;
53

54
PropertyEditor::PropertyEditor(QWidget* parent)
55
    : QTreeView(parent)
56
    , autoexpand(false)
57
    , autoupdate(false)
58
    , committing(false)
59
    , delaybuild(false)
60
    , binding(false)
61
    , checkDocument(false)
62
    , closingEditor(false)
63
    , dragInProgress(false)
64
{
65
    propertyModel = new PropertyModel(this);
66
    setModel(propertyModel);
67

68
    setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
69
    setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
70

71
    delegate = new PropertyItemDelegate(this);
72
    delegate->setItemEditorFactory(new PropertyItemEditorFactory);
73
    setItemDelegate(delegate);
74

75
    setAlternatingRowColors(true);
76
    setRootIsDecorated(false);
77
    setExpandsOnDoubleClick(true);
78

79
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
80
    QStyleOptionViewItem opt = PropertyEditor::viewOptions();
81
#else
82
    QStyleOptionViewItem opt;
83
    initViewItemOption(&opt);
84
#endif
85
    this->background = opt.palette.dark();
86
    this->groupColor = opt.palette.color(QPalette::BrightText);
87

88
    this->_itemBackground.setColor(QColor(0, 0, 0, 0));
89

90
    this->setSelectionMode(QAbstractItemView::ExtendedSelection);
91

92
    // clang-format off
93
    connect(this, &QTreeView::activated, this, &PropertyEditor::onItemActivated);
94
    connect(this, &QTreeView::clicked, this, &PropertyEditor::onItemActivated);
95
    connect(this, &QTreeView::expanded, this, &PropertyEditor::onItemExpanded);
96
    connect(this, &QTreeView::collapsed, this, &PropertyEditor::onItemCollapsed);
97
    connect(propertyModel, &QAbstractItemModel::rowsMoved, this, &PropertyEditor::onRowsMoved);
98
    connect(propertyModel, &QAbstractItemModel::rowsRemoved, this, &PropertyEditor::onRowsRemoved);
99
    // clang-format on
100

101
    setHeaderHidden(true);
102
    viewport()->installEventFilter(this);
103
    viewport()->setMouseTracking(true);
104

105
    auto hGrp = App::GetApplication().GetParameterGroupByPath(
106
        "User parameter:BaseApp/Preferences/DockWindows/PropertyView");
107
    int firstColumnSize = hGrp->GetInt("FirstColumnSize", 0);
108
    if (firstColumnSize != 0) {
109
        header()->resizeSection(0, firstColumnSize);
110
    }
111
}
112

113
PropertyEditor::~PropertyEditor()
114
{
115
    QItemEditorFactory* f = delegate->itemEditorFactory();
116
    delegate->setItemEditorFactory(nullptr);
117
    delete f;
118
}
119

120
void PropertyEditor::setAutomaticExpand(bool v)
121
{
122
    autoexpand = v;
123
}
124

125
bool PropertyEditor::isAutomaticExpand(bool) const
126
{
127
    return autoexpand;
128
}
129

130
void PropertyEditor::onItemExpanded(const QModelIndex& index)
131
{
132
    auto item = static_cast<PropertyItem*>(index.internalPointer());
133
    item->setExpanded(true);
134
    for (int i = 0, c = item->childCount(); i < c; ++i) {
135
        setExpanded(propertyModel->index(i, 0, index), item->child(i)->isExpanded());
136
    }
137
}
138

139
void PropertyEditor::onItemCollapsed(const QModelIndex& index)
140
{
141
    auto item = static_cast<PropertyItem*>(index.internalPointer());
142
    item->setExpanded(false);
143
}
144

145
void PropertyEditor::setAutomaticDocumentUpdate(bool v)
146
{
147
    autoupdate = v;
148
}
149

150
bool PropertyEditor::isAutomaticDocumentUpdate(bool) const
151
{
152
    return autoupdate;
153
}
154

155
QBrush PropertyEditor::groupBackground() const
156
{
157
    return this->background;
158
}
159

160
void PropertyEditor::setGroupBackground(const QBrush& c)
161
{
162
    this->background = c;
163
}
164

165
QColor PropertyEditor::groupTextColor() const
166
{
167
    return this->groupColor;
168
}
169

170
void PropertyEditor::setGroupTextColor(const QColor& c)
171
{
172
    this->groupColor = c;
173
}
174

175
QBrush PropertyEditor::itemBackground() const
176
{
177
    return this->_itemBackground;
178
}
179

180
void PropertyEditor::setItemBackground(const QBrush& c)
181
{
182
    this->_itemBackground = c;
183
}
184

185
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
186
QStyleOptionViewItem PropertyEditor::viewOptions() const
187
{
188
    QStyleOptionViewItem option = QTreeView::viewOptions();
189
    option.showDecorationSelected = true;
190
    return option;
191
}
192
#else
193
void PropertyEditor::initViewItemOption(QStyleOptionViewItem* option) const
194
{
195
    QTreeView::initViewItemOption(option);
196
    option->showDecorationSelected = true;
197
}
198
#endif
199

200
bool PropertyEditor::event(QEvent* event)
201
{
202
    if (event->type() == QEvent::ShortcutOverride) {
203
        auto kevent = static_cast<QKeyEvent*>(event);
204
        Qt::KeyboardModifiers ShiftKeypadModifier = Qt::ShiftModifier | Qt::KeypadModifier;
205
        if (kevent->modifiers() == Qt::NoModifier || kevent->modifiers() == Qt::ShiftModifier
206
            || kevent->modifiers() == Qt::KeypadModifier
207
            || kevent->modifiers() == ShiftKeypadModifier) {
208
            switch (kevent->key()) {
209
                case Qt::Key_Delete:
210
                case Qt::Key_Home:
211
                case Qt::Key_End:
212
                case Qt::Key_Backspace:
213
                case Qt::Key_Left:
214
                case Qt::Key_Right:
215
                    kevent->accept();
216
                default:
217
                    break;
218
            }
219
        }
220
    }
221
    return QTreeView::event(event);
222
}
223

224
void PropertyEditor::commitData(QWidget* editor)
225
{
226
    committing = true;
227
    QTreeView::commitData(editor);
228
    committing = false;
229
    if (delaybuild) {
230
        delaybuild = false;
231
        propertyModel->buildUp(PropertyModel::PropertyList());
232
    }
233
}
234

235
void PropertyEditor::editorDestroyed(QObject* editor)
236
{
237
    QTreeView::editorDestroyed(editor);
238

239
    // When editing expression through context menu, the editor (ExpLineEditor)
240
    // deletes itself when finished, so it won't trigger closeEditor signal. We
241
    // must handle it here to perform auto update.
242
    closeTransaction();
243
}
244

245
void PropertyEditor::currentChanged(const QModelIndex& current, const QModelIndex& previous)
246
{
247
    FC_LOG("current changed " << current.row() << "," << current.column() << "  " << previous.row()
248
                              << "," << previous.column());
249

250
    QTreeView::currentChanged(current, previous);
251

252
    // if (previous.isValid())
253
    //     closePersistentEditor(model()->buddy(previous));
254

255
    // DO NOT activate editor here, use onItemActivate() which response to
256
    // signals of activated and clicked.
257
    //
258
    // if (current.isValid())
259
    //     openPersistentEditor(model()->buddy(current));
260
}
261

262
void PropertyEditor::closeEditor()
263
{
264
    if (editingIndex.isValid()) {
265
        Base::StateLocker guard(closingEditor);
266
        bool hasFocus = activeEditor && activeEditor->hasFocus();
267
#ifdef Q_OS_MACOS
268
        // Brute-force workaround for https://github.com/FreeCAD/FreeCAD/issues/14350
269
        int currentIndex = 0;
270
        QTabBar* tabBar = nullptr;
271
        if (auto mdiArea = Gui::MainWindow::getInstance()->findChild<QMdiArea*>()) {
272
            tabBar = mdiArea->findChild<QTabBar*>();
273
            if (tabBar) {
274
                currentIndex = tabBar->currentIndex();
275
            }
276
        }
277
#endif
278
        closePersistentEditor(editingIndex);
279
#ifdef Q_OS_MACOS
280
        if (tabBar) {
281
            tabBar->setCurrentIndex(currentIndex);
282
        }
283
#endif
284
        editingIndex = QPersistentModelIndex();
285
        activeEditor = nullptr;
286
        if (hasFocus) {
287
            setFocus();
288
        }
289
    }
290
}
291

292
void PropertyEditor::openEditor(const QModelIndex& index)
293
{
294
    if (editingIndex == index && activeEditor) {
295
        return;
296
    }
297

298
    closeEditor();
299

300
    openPersistentEditor(model()->buddy(index));
301

302
    if (!editingIndex.isValid() || !autoupdate) {
303
        return;
304
    }
305

306
    auto& app = App::GetApplication();
307
    if (app.getActiveTransaction()) {
308
        FC_LOG("editor already transacting " << app.getActiveTransaction());
309
        return;
310
    }
311
    auto item = static_cast<PropertyItem*>(editingIndex.internalPointer());
312
    auto items = item->getPropertyData();
313
    for (auto propItem = item->parent(); items.empty() && propItem; propItem = propItem->parent()) {
314
        items = propItem->getPropertyData();
315
    }
316
    if (items.empty()) {
317
        FC_LOG("editor no item");
318
        return;
319
    }
320
    auto prop = items[0];
321
    auto parent = prop->getContainer();
322
    auto obj = Base::freecad_dynamic_cast<App::DocumentObject>(parent);
323
    if (!obj) {
324
        auto view = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(parent);
325
        if (view) {
326
            obj = view->getObject();
327
        }
328
    }
329
    if (!obj || !obj->getDocument()) {
330
        FC_LOG("invalid object");
331
        return;
332
    }
333
    if (obj->getDocument()->hasPendingTransaction()) {
334
        FC_LOG("pending transaction");
335
        return;
336
    }
337
    std::ostringstream str;
338
    str << tr("Edit").toUtf8().constData() << ' ';
339
    for (auto prop : items) {
340
        if (prop->getContainer() != obj) {
341
            obj = nullptr;
342
            break;
343
        }
344
    }
345
    if (obj && obj->isAttachedToDocument()) {
346
        str << obj->getNameInDocument() << '.';
347
    }
348
    else {
349
        str << tr("property").toUtf8().constData() << ' ';
350
    }
351
    str << prop->getName();
352
    if (items.size() > 1) {
353
        str << "...";
354
    }
355
    transactionID = app.setActiveTransaction(str.str().c_str());
356
    FC_LOG("editor transaction " << app.getActiveTransaction());
357
}
358

359
void PropertyEditor::onItemActivated(const QModelIndex& index)
360
{
361
    if (index.column() != 1) {
362
        return;
363
    }
364
    openEditor(index);
365
}
366

367
void PropertyEditor::recomputeDocument(App::Document* doc)
368
{
369
    try {
370
        if (doc && !doc->isTransactionEmpty()) {
371
            // Between opening and committing a transaction a recompute
372
            // could already have been done
373
            if (doc->isTouched()) {
374
                doc->recompute();
375
            }
376
        }
377
    }
378
    // do not re-throw
379
    catch (const Base::Exception& e) {
380
        e.ReportException();
381
    }
382
    catch (const std::exception& e) {
383
        Base::Console().Error(
384
            "Unhandled std::exception caught in PropertyEditor::recomputeDocument.\n"
385
            "The error message is: %s\n",
386
            e.what());
387
    }
388
    catch (...) {
389
        Base::Console().Error(
390
            "Unhandled unknown exception caught in PropertyEditor::recomputeDocument.\n");
391
    }
392
}
393

394
void PropertyEditor::closeTransaction()
395
{
396
    int tid = 0;
397
    if (App::GetApplication().getActiveTransaction(&tid) && tid == transactionID) {
398
        if (autoupdate) {
399
            App::Document* doc = App::GetApplication().getActiveDocument();
400
            recomputeDocument(doc);
401
        }
402
        App::GetApplication().closeActiveTransaction();
403
    }
404
}
405

406
void PropertyEditor::closeEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint)
407
{
408
    if (closingEditor) {
409
        return;
410
    }
411

412
    if (removingRows) {
413
        // When removing rows, QTreeView will temporary hide the editor which
414
        // will trigger Event::FocusOut and subsequently trigger call of
415
        // closeEditor() here. Since we are using persistent editor, QTreeView
416
        // will not destroy the editor. But we still needs to call
417
        // QTreeView::closeEditor() here, in case the editor belongs to the
418
        // removed rows.
419
        QTreeView::closeEditor(editor, hint);
420
        return;
421
    }
422

423
    closeTransaction();
424

425
    // If we are not removing rows, then QTreeView::closeEditor() does nothing
426
    // because we are using persistent editor, so we have to call our own
427
    // version of closeEditor()
428
    this->closeEditor();
429

430
    QModelIndex indexSaved = currentIndex();
431

432
    if (indexSaved.column() == 0) {
433
        // Calling setCurrentIndex() to make sure we focus on column 1 instead of 0.
434
        setCurrentIndex(propertyModel->buddy(indexSaved));
435
    }
436

437
    QModelIndex lastIndex = indexSaved;
438
    bool wrapped = false;
439
    do {
440
        QModelIndex index;
441
        if (hint == QAbstractItemDelegate::EditNextItem) {
442
            index = moveCursor(MoveDown, Qt::NoModifier);
443
        }
444
        else if (hint == QAbstractItemDelegate::EditPreviousItem) {
445
            index = moveCursor(MoveUp, Qt::NoModifier);
446
        }
447
        else {
448
            break;
449
        }
450
        if (!index.isValid() || index == lastIndex) {
451
            if (wrapped) {
452
                setCurrentIndex(propertyModel->buddy(indexSaved));
453
                break;
454
            }
455
            wrapped = true;
456
            if (hint == QAbstractItemDelegate::EditNextItem) {
457
                index = moveCursor(MoveHome, Qt::NoModifier);
458
            }
459
            else {
460
                index = moveCursor(MoveEnd, Qt::NoModifier);
461
            }
462
            if (!index.isValid() || index == indexSaved) {
463
                break;
464
            }
465
        }
466
        lastIndex = index;
467
        setCurrentIndex(propertyModel->buddy(index));
468

469
        auto item = static_cast<PropertyItem*>(index.internalPointer());
470
        // Skip readonly item, because the editor will be disabled and hence
471
        // does not accept focus, and in turn break Tab/Backtab navigation.
472
        if (item && item->isReadOnly()) {
473
            continue;
474
        }
475

476
        openEditor(index);
477

478
    } while (!editingIndex.isValid());
479
}
480

481
void PropertyEditor::reset()
482
{
483
    QTreeView::reset();
484

485
    closeTransaction();
486

487
    QModelIndex parent;
488
    int numRows = propertyModel->rowCount(parent);
489
    for (int i = 0; i < numRows; ++i) {
490
        QModelIndex index = propertyModel->index(i, 0, parent);
491
        auto item = static_cast<PropertyItem*>(index.internalPointer());
492
        if (item->childCount() == 0) {
493
            if (item->isSeparator()) {
494
                setRowHidden(i, parent, true);
495
            }
496
        }
497
        else {
498
            setEditorMode(index, 0, item->childCount() - 1);
499
        }
500
        if (item->isExpanded()) {
501
            setExpanded(index, true);
502
        }
503
    }
504
}
505

506
void PropertyEditor::onRowsMoved(const QModelIndex& parent,
507
                                 int start,
508
                                 int end,
509
                                 const QModelIndex& dst,
510
                                 int)
511
{
512
    if (parent != dst) {
513
        auto item = static_cast<PropertyItem*>(parent.internalPointer());
514
        if (item && item->isSeparator() && item->childCount() == 0) {
515
            setRowHidden(parent.row(), propertyModel->parent(parent), true);
516
        }
517
        item = static_cast<PropertyItem*>(dst.internalPointer());
518
        if (item && item->isSeparator() && item->childCount() == end - start + 1) {
519
            setRowHidden(dst.row(), propertyModel->parent(dst), false);
520
            setExpanded(dst, true);
521
        }
522
    }
523
}
524

525
void PropertyEditor::rowsInserted(const QModelIndex& parent, int start, int end)
526
{
527
    QTreeView::rowsInserted(parent, start, end);
528

529
    auto item = static_cast<PropertyItem*>(parent.internalPointer());
530
    if (item && item->isSeparator() && item->childCount() == end - start + 1) {
531
        setRowHidden(parent.row(), propertyModel->parent(parent), false);
532
        if (item->isExpanded()) {
533
            setExpanded(parent, true);
534
        }
535
    }
536

537
    for (int i = start; i < end; ++i) {
538
        QModelIndex index = propertyModel->index(i, 0, parent);
539
        auto child = static_cast<PropertyItem*>(index.internalPointer());
540
        if (child->isSeparator()) {
541
            // Set group header rows to span all columns
542
            setFirstColumnSpanned(i, parent, true);
543
        }
544
        if (child->isExpanded()) {
545
            setExpanded(index, true);
546
        }
547
    }
548

549
    if (parent.isValid()) {
550
        setEditorMode(parent, start, end);
551
    }
552
}
553

554
void PropertyEditor::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
555
{
556
    QTreeView::rowsAboutToBeRemoved(parent, start, end);
557

558
    auto item = static_cast<PropertyItem*>(parent.internalPointer());
559
    if (item && item->isSeparator() && item->childCount() == end - start + 1) {
560
        setRowHidden(parent.row(), propertyModel->parent(parent), true);
561
    }
562

563
    if (editingIndex.isValid()) {
564
        if (editingIndex.row() >= start && editingIndex.row() <= end) {
565
            closeTransaction();
566
        }
567
        else {
568
            removingRows = 1;
569
            for (QWidget* w = qApp->focusWidget(); w; w = w->parentWidget()) {
570
                if (w == activeEditor) {
571
                    removingRows = -1;
572
                    break;
573
                }
574
            }
575
        }
576
    }
577
}
578

579
void PropertyEditor::onRowsRemoved(const QModelIndex&, int, int)
580
{
581
    if (removingRows < 0 && activeEditor) {
582
        activeEditor->setFocus();
583
    }
584
    removingRows = 0;
585
}
586

587
void PropertyEditor::drawBranches(QPainter* painter,
588
                                  const QRect& rect,
589
                                  const QModelIndex& index) const
590
{
591
    QTreeView::drawBranches(painter, rect, index);
592

593
    auto property = static_cast<PropertyItem*>(index.internalPointer());
594

595
    if (property && property->isSeparator()) {
596
        painter->fillRect(rect, this->background);
597
    }
598
}
599

600
void Gui::PropertyEditor::PropertyEditor::drawRow(QPainter* painter,
601
                                                  const QStyleOptionViewItem& options,
602
                                                  const QModelIndex& index) const
603
{
604
    // render background also for non alternate rows based on the `itemBackground` property.
605
    painter->fillRect(options.rect, itemBackground());
606

607
    QTreeView::drawRow(painter, options, index);
608
}
609

610
void PropertyEditor::buildUp(PropertyModel::PropertyList&& props, bool _checkDocument)
611
{
612
    checkDocument = _checkDocument;
613

614
    if (committing) {
615
        Base::Console().Warning(
616
            "While committing the data to the property the selection has changed.\n");
617
        delaybuild = true;
618
        return;
619
    }
620

621
    // Do not close transaction here, because we are now doing incremental
622
    // update in PropertyModel::buildUp()
623
    //
624
    // closeTransaction();
625

626
    QModelIndex index = this->currentIndex();
627
    QStringList propertyPath = propertyModel->propertyPathFromIndex(index);
628
    if (!propertyPath.isEmpty()) {
629
        this->selectedProperty = propertyPath;
630
    }
631
    propertyModel->buildUp(props);
632
    if (!this->selectedProperty.isEmpty()) {
633
        QModelIndex index = propertyModel->propertyIndexFromPath(this->selectedProperty);
634
        this->setCurrentIndex(index);
635
    }
636

637
    propList = std::move(props);
638
    propOwners.clear();
639
    for (auto& v : propList) {
640
        for (auto prop : v.second) {
641
            auto container = prop->getContainer();
642
            if (!container) {
643
                continue;
644
            }
645
            // Include document to get proper handling in PropertyView::slotDeleteDocument()
646
            if (checkDocument && container->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
647
                propOwners.insert(static_cast<App::DocumentObject*>(container)->getDocument());
648
            }
649
            propOwners.insert(container);
650
        }
651
    }
652

653
    if (autoexpand) {
654
        expandAll();
655
    }
656
}
657

658
void PropertyEditor::updateProperty(const App::Property& prop)
659
{
660
    // forward this to the model if the property is changed from outside
661
    if (!committing) {
662
        propertyModel->updateProperty(prop);
663
    }
664
}
665

666
void PropertyEditor::setEditorMode(const QModelIndex& parent, int start, int end)
667
{
668
    int column = 1;
669
    for (int i = start; i <= end; i++) {
670
        QModelIndex item = propertyModel->index(i, column, parent);
671
        auto propItem = static_cast<PropertyItem*>(item.internalPointer());
672
        if (!PropertyView::showAll() && propItem && propItem->testStatus(App::Property::Hidden)) {
673
            setRowHidden(i, parent, true);
674
        }
675
    }
676
}
677

678
void PropertyEditor::removeProperty(const App::Property& prop)
679
{
680
    for (PropertyModel::PropertyList::iterator it = propList.begin(); it != propList.end(); ++it) {
681
        // find the given property in the list and remove it if it's there
682
        std::vector<App::Property*>::iterator pos =
683
            std::find(it->second.begin(), it->second.end(), &prop);
684
        if (pos != it->second.end()) {
685
            it->second.erase(pos);
686
            // if the last property of this name is removed then also remove the whole group
687
            if (it->second.empty()) {
688
                propList.erase(it);
689
            }
690
            propertyModel->removeProperty(prop);
691
            break;
692
        }
693
    }
694
}
695

696
enum MenuAction
697
{
698
    MA_AutoExpand,
699
    MA_ShowHidden,
700
    MA_Expression,
701
    MA_RemoveProp,
702
    MA_AddProp,
703
    MA_EditPropGroup,
704
    MA_Transient,
705
    MA_Output,
706
    MA_NoRecompute,
707
    MA_ReadOnly,
708
    MA_Hidden,
709
    MA_Touched,
710
    MA_EvalOnRestore,
711
    MA_CopyOnChange,
712
};
713

714
void PropertyEditor::contextMenuEvent(QContextMenuEvent*)
715
{
716
    QMenu menu;
717
    QAction* autoExpand = nullptr;
718

719
    auto contextIndex = currentIndex();
720

721
    // acquiring the selected properties
722
    std::unordered_set<App::Property*> props;
723
    const auto indexes = selectedIndexes();
724
    for (const auto& index : indexes) {
725
        auto item = static_cast<PropertyItem*>(index.internalPointer());
726
        if (item->isSeparator()) {
727
            continue;
728
        }
729
        for (auto parent = item; parent; parent = parent->parent()) {
730
            const auto& ps = parent->getPropertyData();
731
            if (!ps.empty()) {
732
                props.insert(ps.begin(), ps.end());
733
                break;
734
            }
735
        }
736
    }
737

738
    // add property
739
    menu.addAction(tr("Add property"))->setData(QVariant(MA_AddProp));
740
    if (!props.empty() && std::all_of(props.begin(), props.end(), [](auto prop) {
741
            return prop->testStatus(App::Property::PropDynamic)
742
                && !boost::starts_with(prop->getName(), prop->getGroup());
743
        })) {
744
        menu.addAction(tr("Rename property group"))->setData(QVariant(MA_EditPropGroup));
745
    }
746

747
    // remove property
748
    bool canRemove = !props.empty();
749
    unsigned long propType = 0;
750
    unsigned long propStatus = 0xffffffff;
751
    for (auto prop : props) {
752
        propType |= prop->getType();
753
        propStatus &= prop->getStatus();
754
        if (!prop->testStatus(App::Property::PropDynamic)
755
            || prop->testStatus(App::Property::LockDynamic)) {
756
            canRemove = false;
757
        }
758
    }
759
    if (canRemove) {
760
        menu.addAction(tr("Remove property"))->setData(QVariant(MA_RemoveProp));
761
    }
762

763
    // add a separator between adding/removing properties and the rest
764
    menu.addSeparator();
765

766
    // show all
767
    QAction* showHidden = menu.addAction(tr("Show hidden"));
768
    showHidden->setCheckable(true);
769
    showHidden->setChecked(PropertyView::showAll());
770
    showHidden->setData(QVariant(MA_ShowHidden));
771

772
    // auto expand
773
    autoExpand = menu.addAction(tr("Auto expand"));
774
    autoExpand->setCheckable(true);
775
    autoExpand->setChecked(autoexpand);
776
    autoExpand->setData(QVariant(MA_AutoExpand));
777

778
    // expression
779
    if (props.size() == 1) {
780
        auto item = static_cast<PropertyItem*>(contextIndex.internalPointer());
781
        auto prop = *props.begin();
782
        if (item->isBound() && !prop->isDerivedFrom(App::PropertyExpressionEngine::getClassTypeId())
783
            && !prop->isReadOnly() && !prop->testStatus(App::Property::Immutable)
784
            && !(prop->getType() & App::Prop_ReadOnly)) {
785
            contextIndex = propertyModel->buddy(contextIndex);
786
            setCurrentIndex(contextIndex);
787
            // menu.addSeparator();
788
            menu.addAction(tr("Expression..."))->setData(QVariant(MA_Expression));
789
        }
790
    }
791

792
    // the various flags
793
    if (!props.empty()) {
794
        menu.addSeparator();
795

796
        // the subMenu is allocated on the heap but managed by menu.
797
        auto subMenu = new QMenu(QString::fromLatin1("Status"), &menu);
798

799
        QAction* action;
800
        QString text;
801
#define _ACTION_SETUP(_name)                                                                       \
802
    do {                                                                                           \
803
        text = tr(#_name);                                                                         \
804
        action = subMenu->addAction(text);                                                         \
805
        action->setData(QVariant(MA_##_name));                                                     \
806
        action->setCheckable(true);                                                                \
807
        if (propStatus & (1 << App::Property::_name))                                              \
808
            action->setChecked(true);                                                              \
809
    } while (0)
810
#define ACTION_SETUP(_name)                                                                        \
811
    do {                                                                                           \
812
        _ACTION_SETUP(_name);                                                                      \
813
        if (propType & App::Prop_##_name) {                                                        \
814
            action->setText(text + QString::fromLatin1(" *"));                                     \
815
            action->setChecked(true);                                                              \
816
        }                                                                                          \
817
    } while (0)
818

819
        ACTION_SETUP(Hidden);
820
        ACTION_SETUP(Output);
821
        ACTION_SETUP(NoRecompute);
822
        ACTION_SETUP(ReadOnly);
823
        ACTION_SETUP(Transient);
824
        _ACTION_SETUP(Touched);
825
        _ACTION_SETUP(EvalOnRestore);
826
        _ACTION_SETUP(CopyOnChange);
827

828
        menu.addMenu(subMenu);
829
    }
830

831
    auto action = menu.exec(QCursor::pos());
832
    if (!action) {
833
        return;
834
    }
835

836
    switch (action->data().toInt()) {
837
        case MA_AutoExpand:
838
            if (autoExpand) {
839
                // Variable autoExpand should not be null when we arrive here, but
840
                // since we explicitly initialize the variable to nullptr, a check
841
                // nonetheless.
842
                autoexpand = autoExpand->isChecked();
843
                if (autoexpand) {
844
                    expandAll();
845
                }
846
            }
847
            return;
848
        case MA_ShowHidden:
849
            PropertyView::setShowAll(action->isChecked());
850
            return;
851
#define ACTION_CHECK(_name)                                                                        \
852
    case MA_##_name:                                                                               \
853
        for (auto prop : props)                                                                    \
854
            prop->setStatus(App::Property::_name, action->isChecked());                            \
855
        break
856
            ACTION_CHECK(Transient);
857
            ACTION_CHECK(ReadOnly);
858
            ACTION_CHECK(Output);
859
            ACTION_CHECK(Hidden);
860
            ACTION_CHECK(EvalOnRestore);
861
            ACTION_CHECK(CopyOnChange);
862
        case MA_Touched:
863
            for (auto prop : props) {
864
                if (action->isChecked()) {
865
                    prop->touch();
866
                }
867
                else {
868
                    prop->purgeTouched();
869
                }
870
            }
871
            break;
872
        case MA_Expression:
873
            if (contextIndex == currentIndex()) {
874
                Base::FlagToggler<> flag(binding);
875
                closeEditor();
876
                openEditor(contextIndex);
877
            }
878
            break;
879
        case MA_AddProp: {
880
            App::AutoTransaction committer("Add property");
881
            std::unordered_set<App::PropertyContainer*> containers;
882
            auto sels = Gui::Selection().getSelection("*");
883
            if (sels.size() == 1) {
884
                containers.insert(sels[0].pObject);
885
            }
886
            else {
887
                for (auto prop : props) {
888
                    containers.insert(prop->getContainer());
889
                }
890
            }
891
            Gui::Dialog::DlgAddProperty dlg(Gui::getMainWindow(), std::move(containers));
892
            dlg.exec();
893
            return;
894
        }
895
        case MA_EditPropGroup: {
896
            // This operation is not undoable yet.
897
            const char* groupName = (*props.begin())->getGroup();
898
            if (!groupName) {
899
                groupName = "Base";
900
            }
901
            QString res = QInputDialog::getText(Gui::getMainWindow(),
902
                                                tr("Rename property group"),
903
                                                tr("Group name:"),
904
                                                QLineEdit::Normal,
905
                                                QString::fromUtf8(groupName));
906
            if (res.size()) {
907
                std::string group = res.toUtf8().constData();
908
                for (auto prop : props) {
909
                    prop->getContainer()->changeDynamicProperty(prop, group.c_str(), nullptr);
910
                }
911
                buildUp(PropertyModel::PropertyList(propList), checkDocument);
912
            }
913
            return;
914
        }
915
        case MA_RemoveProp: {
916
            App::AutoTransaction committer("Remove property");
917
            for (auto prop : props) {
918
                try {
919
                    prop->getContainer()->removeDynamicProperty(prop->getName());
920
                }
921
                catch (Base::Exception& e) {
922
                    e.ReportException();
923
                }
924
            }
925
            break;
926
        }
927
        default:
928
            break;
929
    }
930
}
931

932

933
bool PropertyEditor::eventFilter(QObject* object, QEvent* event)
934
{
935
    if (object == viewport()) {
936
        QMouseEvent* mouse_event = dynamic_cast<QMouseEvent*>(event);
937
        if (mouse_event) {
938
            if (mouse_event->type() == QEvent::MouseMove) {
939
                if (dragInProgress) {  // apply dragging
940
                    QHeaderView* header_view = header();
941
                    int delta = mouse_event->pos().x() - dragPreviousPos;
942
                    dragPreviousPos = mouse_event->pos().x();
943
                    // using minimal size = dragSensibility * 2 to prevent collapsing
944
                    header_view->resizeSection(
945
                        dragSection,
946
                        qMax(dragSensibility * 2, header_view->sectionSize(dragSection) + delta));
947
                    return true;
948
                }
949
                else {  // set mouse cursor shape
950
                    if (indexResizable(mouse_event->pos()).isValid()) {
951
                        viewport()->setCursor(Qt::SplitHCursor);
952
                    }
953
                    else {
954
                        viewport()->setCursor(QCursor());
955
                    }
956
                }
957
            }
958
            else if (mouse_event->type() == QEvent::MouseButtonPress
959
                     && mouse_event->button() == Qt::LeftButton && !dragInProgress) {
960
                if (indexResizable(mouse_event->pos()).isValid()) {
961
                    dragInProgress = true;
962
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
963
                    dragPreviousPos = mouse_event->x();
964
#else
965
                    dragPreviousPos = mouse_event->position().toPoint().x();
966
#endif
967
                    dragSection = indexResizable(mouse_event->pos()).column();
968
                    return true;
969
                }
970
            }
971
            else if (mouse_event->type() == QEvent::MouseButtonRelease
972
                     && mouse_event->button() == Qt::LeftButton && dragInProgress) {
973
                dragInProgress = false;
974

975
                auto hGrp = App::GetApplication().GetParameterGroupByPath(
976
                    "User parameter:BaseApp/Preferences/DockWindows/PropertyView");
977
                hGrp->SetInt("FirstColumnSize", header()->sectionSize(0));
978
                return true;
979
            }
980
        }
981
    }
982
    return false;
983
}
984

985
QModelIndex PropertyEditor::indexResizable(QPoint mouse_pos)
986
{
987
    QModelIndex index = indexAt(mouse_pos - QPoint(dragSensibility + 1, 0));
988
    if (index.isValid()) {
989
        if (qAbs(visualRect(index).right() - mouse_pos.x()) < dragSensibility
990
            && header()->sectionResizeMode(index.column()) == QHeaderView::Interactive) {
991
            return index;
992
        }
993
    }
994
    return QModelIndex();
995
}
996

997
#include "moc_PropertyEditor.cpp"
998

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

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

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

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