FreeCAD
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
50FC_LOG_LEVEL_INIT("PropertyView", true, true)
51
52using namespace Gui::PropertyEditor;
53
54PropertyEditor::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{
65propertyModel = new PropertyModel(this);
66setModel(propertyModel);
67
68setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
69setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
70
71delegate = new PropertyItemDelegate(this);
72delegate->setItemEditorFactory(new PropertyItemEditorFactory);
73setItemDelegate(delegate);
74
75setAlternatingRowColors(true);
76setRootIsDecorated(false);
77setExpandsOnDoubleClick(true);
78
79#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
80QStyleOptionViewItem opt = PropertyEditor::viewOptions();
81#else
82QStyleOptionViewItem opt;
83initViewItemOption(&opt);
84#endif
85this->background = opt.palette.dark();
86this->groupColor = opt.palette.color(QPalette::BrightText);
87
88this->_itemBackground.setColor(QColor(0, 0, 0, 0));
89
90this->setSelectionMode(QAbstractItemView::ExtendedSelection);
91
92// clang-format off
93connect(this, &QTreeView::activated, this, &PropertyEditor::onItemActivated);
94connect(this, &QTreeView::clicked, this, &PropertyEditor::onItemActivated);
95connect(this, &QTreeView::expanded, this, &PropertyEditor::onItemExpanded);
96connect(this, &QTreeView::collapsed, this, &PropertyEditor::onItemCollapsed);
97connect(propertyModel, &QAbstractItemModel::rowsMoved, this, &PropertyEditor::onRowsMoved);
98connect(propertyModel, &QAbstractItemModel::rowsRemoved, this, &PropertyEditor::onRowsRemoved);
99// clang-format on
100
101setHeaderHidden(true);
102viewport()->installEventFilter(this);
103viewport()->setMouseTracking(true);
104
105auto hGrp = App::GetApplication().GetParameterGroupByPath(
106"User parameter:BaseApp/Preferences/DockWindows/PropertyView");
107int firstColumnSize = hGrp->GetInt("FirstColumnSize", 0);
108if (firstColumnSize != 0) {
109header()->resizeSection(0, firstColumnSize);
110}
111}
112
113PropertyEditor::~PropertyEditor()
114{
115QItemEditorFactory* f = delegate->itemEditorFactory();
116delegate->setItemEditorFactory(nullptr);
117delete f;
118}
119
120void PropertyEditor::setAutomaticExpand(bool v)
121{
122autoexpand = v;
123}
124
125bool PropertyEditor::isAutomaticExpand(bool) const
126{
127return autoexpand;
128}
129
130void PropertyEditor::onItemExpanded(const QModelIndex& index)
131{
132auto item = static_cast<PropertyItem*>(index.internalPointer());
133item->setExpanded(true);
134for (int i = 0, c = item->childCount(); i < c; ++i) {
135setExpanded(propertyModel->index(i, 0, index), item->child(i)->isExpanded());
136}
137}
138
139void PropertyEditor::onItemCollapsed(const QModelIndex& index)
140{
141auto item = static_cast<PropertyItem*>(index.internalPointer());
142item->setExpanded(false);
143}
144
145void PropertyEditor::setAutomaticDocumentUpdate(bool v)
146{
147autoupdate = v;
148}
149
150bool PropertyEditor::isAutomaticDocumentUpdate(bool) const
151{
152return autoupdate;
153}
154
155QBrush PropertyEditor::groupBackground() const
156{
157return this->background;
158}
159
160void PropertyEditor::setGroupBackground(const QBrush& c)
161{
162this->background = c;
163}
164
165QColor PropertyEditor::groupTextColor() const
166{
167return this->groupColor;
168}
169
170void PropertyEditor::setGroupTextColor(const QColor& c)
171{
172this->groupColor = c;
173}
174
175QBrush PropertyEditor::itemBackground() const
176{
177return this->_itemBackground;
178}
179
180void PropertyEditor::setItemBackground(const QBrush& c)
181{
182this->_itemBackground = c;
183}
184
185#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
186QStyleOptionViewItem PropertyEditor::viewOptions() const
187{
188QStyleOptionViewItem option = QTreeView::viewOptions();
189option.showDecorationSelected = true;
190return option;
191}
192#else
193void PropertyEditor::initViewItemOption(QStyleOptionViewItem* option) const
194{
195QTreeView::initViewItemOption(option);
196option->showDecorationSelected = true;
197}
198#endif
199
200bool PropertyEditor::event(QEvent* event)
201{
202if (event->type() == QEvent::ShortcutOverride) {
203auto kevent = static_cast<QKeyEvent*>(event);
204Qt::KeyboardModifiers ShiftKeypadModifier = Qt::ShiftModifier | Qt::KeypadModifier;
205if (kevent->modifiers() == Qt::NoModifier || kevent->modifiers() == Qt::ShiftModifier
206|| kevent->modifiers() == Qt::KeypadModifier
207|| kevent->modifiers() == ShiftKeypadModifier) {
208switch (kevent->key()) {
209case Qt::Key_Delete:
210case Qt::Key_Home:
211case Qt::Key_End:
212case Qt::Key_Backspace:
213case Qt::Key_Left:
214case Qt::Key_Right:
215kevent->accept();
216default:
217break;
218}
219}
220}
221return QTreeView::event(event);
222}
223
224void PropertyEditor::commitData(QWidget* editor)
225{
226committing = true;
227QTreeView::commitData(editor);
228committing = false;
229if (delaybuild) {
230delaybuild = false;
231propertyModel->buildUp(PropertyModel::PropertyList());
232}
233}
234
235void PropertyEditor::editorDestroyed(QObject* editor)
236{
237QTreeView::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.
242closeTransaction();
243}
244
245void PropertyEditor::currentChanged(const QModelIndex& current, const QModelIndex& previous)
246{
247FC_LOG("current changed " << current.row() << "," << current.column() << " " << previous.row()
248<< "," << previous.column());
249
250QTreeView::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
262void PropertyEditor::closeEditor()
263{
264if (editingIndex.isValid()) {
265Base::StateLocker guard(closingEditor);
266bool hasFocus = activeEditor && activeEditor->hasFocus();
267#ifdef Q_OS_MACOS
268// Brute-force workaround for https://github.com/FreeCAD/FreeCAD/issues/14350
269int currentIndex = 0;
270QTabBar* tabBar = nullptr;
271if (auto mdiArea = Gui::MainWindow::getInstance()->findChild<QMdiArea*>()) {
272tabBar = mdiArea->findChild<QTabBar*>();
273if (tabBar) {
274currentIndex = tabBar->currentIndex();
275}
276}
277#endif
278closePersistentEditor(editingIndex);
279#ifdef Q_OS_MACOS
280if (tabBar) {
281tabBar->setCurrentIndex(currentIndex);
282}
283#endif
284editingIndex = QPersistentModelIndex();
285activeEditor = nullptr;
286if (hasFocus) {
287setFocus();
288}
289}
290}
291
292void PropertyEditor::openEditor(const QModelIndex& index)
293{
294if (editingIndex == index && activeEditor) {
295return;
296}
297
298closeEditor();
299
300openPersistentEditor(model()->buddy(index));
301
302if (!editingIndex.isValid() || !autoupdate) {
303return;
304}
305
306auto& app = App::GetApplication();
307if (app.getActiveTransaction()) {
308FC_LOG("editor already transacting " << app.getActiveTransaction());
309return;
310}
311auto item = static_cast<PropertyItem*>(editingIndex.internalPointer());
312auto items = item->getPropertyData();
313for (auto propItem = item->parent(); items.empty() && propItem; propItem = propItem->parent()) {
314items = propItem->getPropertyData();
315}
316if (items.empty()) {
317FC_LOG("editor no item");
318return;
319}
320auto prop = items[0];
321auto parent = prop->getContainer();
322auto obj = Base::freecad_dynamic_cast<App::DocumentObject>(parent);
323if (!obj) {
324auto view = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(parent);
325if (view) {
326obj = view->getObject();
327}
328}
329if (!obj || !obj->getDocument()) {
330FC_LOG("invalid object");
331return;
332}
333if (obj->getDocument()->hasPendingTransaction()) {
334FC_LOG("pending transaction");
335return;
336}
337std::ostringstream str;
338str << tr("Edit").toUtf8().constData() << ' ';
339for (auto prop : items) {
340if (prop->getContainer() != obj) {
341obj = nullptr;
342break;
343}
344}
345if (obj && obj->isAttachedToDocument()) {
346str << obj->getNameInDocument() << '.';
347}
348else {
349str << tr("property").toUtf8().constData() << ' ';
350}
351str << prop->getName();
352if (items.size() > 1) {
353str << "...";
354}
355transactionID = app.setActiveTransaction(str.str().c_str());
356FC_LOG("editor transaction " << app.getActiveTransaction());
357}
358
359void PropertyEditor::onItemActivated(const QModelIndex& index)
360{
361if (index.column() != 1) {
362return;
363}
364openEditor(index);
365}
366
367void PropertyEditor::recomputeDocument(App::Document* doc)
368{
369try {
370if (doc && !doc->isTransactionEmpty()) {
371// Between opening and committing a transaction a recompute
372// could already have been done
373if (doc->isTouched()) {
374doc->recompute();
375}
376}
377}
378// do not re-throw
379catch (const Base::Exception& e) {
380e.ReportException();
381}
382catch (const std::exception& e) {
383Base::Console().Error(
384"Unhandled std::exception caught in PropertyEditor::recomputeDocument.\n"
385"The error message is: %s\n",
386e.what());
387}
388catch (...) {
389Base::Console().Error(
390"Unhandled unknown exception caught in PropertyEditor::recomputeDocument.\n");
391}
392}
393
394void PropertyEditor::closeTransaction()
395{
396int tid = 0;
397if (App::GetApplication().getActiveTransaction(&tid) && tid == transactionID) {
398if (autoupdate) {
399App::Document* doc = App::GetApplication().getActiveDocument();
400recomputeDocument(doc);
401}
402App::GetApplication().closeActiveTransaction();
403}
404}
405
406void PropertyEditor::closeEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint)
407{
408if (closingEditor) {
409return;
410}
411
412if (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.
419QTreeView::closeEditor(editor, hint);
420return;
421}
422
423closeTransaction();
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()
428this->closeEditor();
429
430QModelIndex indexSaved = currentIndex();
431
432if (indexSaved.column() == 0) {
433// Calling setCurrentIndex() to make sure we focus on column 1 instead of 0.
434setCurrentIndex(propertyModel->buddy(indexSaved));
435}
436
437QModelIndex lastIndex = indexSaved;
438bool wrapped = false;
439do {
440QModelIndex index;
441if (hint == QAbstractItemDelegate::EditNextItem) {
442index = moveCursor(MoveDown, Qt::NoModifier);
443}
444else if (hint == QAbstractItemDelegate::EditPreviousItem) {
445index = moveCursor(MoveUp, Qt::NoModifier);
446}
447else {
448break;
449}
450if (!index.isValid() || index == lastIndex) {
451if (wrapped) {
452setCurrentIndex(propertyModel->buddy(indexSaved));
453break;
454}
455wrapped = true;
456if (hint == QAbstractItemDelegate::EditNextItem) {
457index = moveCursor(MoveHome, Qt::NoModifier);
458}
459else {
460index = moveCursor(MoveEnd, Qt::NoModifier);
461}
462if (!index.isValid() || index == indexSaved) {
463break;
464}
465}
466lastIndex = index;
467setCurrentIndex(propertyModel->buddy(index));
468
469auto 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.
472if (item && item->isReadOnly()) {
473continue;
474}
475
476openEditor(index);
477
478} while (!editingIndex.isValid());
479}
480
481void PropertyEditor::reset()
482{
483QTreeView::reset();
484
485closeTransaction();
486
487QModelIndex parent;
488int numRows = propertyModel->rowCount(parent);
489for (int i = 0; i < numRows; ++i) {
490QModelIndex index = propertyModel->index(i, 0, parent);
491auto item = static_cast<PropertyItem*>(index.internalPointer());
492if (item->childCount() == 0) {
493if (item->isSeparator()) {
494setRowHidden(i, parent, true);
495}
496}
497else {
498setEditorMode(index, 0, item->childCount() - 1);
499}
500if (item->isExpanded()) {
501setExpanded(index, true);
502}
503}
504}
505
506void PropertyEditor::onRowsMoved(const QModelIndex& parent,
507int start,
508int end,
509const QModelIndex& dst,
510int)
511{
512if (parent != dst) {
513auto item = static_cast<PropertyItem*>(parent.internalPointer());
514if (item && item->isSeparator() && item->childCount() == 0) {
515setRowHidden(parent.row(), propertyModel->parent(parent), true);
516}
517item = static_cast<PropertyItem*>(dst.internalPointer());
518if (item && item->isSeparator() && item->childCount() == end - start + 1) {
519setRowHidden(dst.row(), propertyModel->parent(dst), false);
520setExpanded(dst, true);
521}
522}
523}
524
525void PropertyEditor::rowsInserted(const QModelIndex& parent, int start, int end)
526{
527QTreeView::rowsInserted(parent, start, end);
528
529auto item = static_cast<PropertyItem*>(parent.internalPointer());
530if (item && item->isSeparator() && item->childCount() == end - start + 1) {
531setRowHidden(parent.row(), propertyModel->parent(parent), false);
532if (item->isExpanded()) {
533setExpanded(parent, true);
534}
535}
536
537for (int i = start; i < end; ++i) {
538QModelIndex index = propertyModel->index(i, 0, parent);
539auto child = static_cast<PropertyItem*>(index.internalPointer());
540if (child->isSeparator()) {
541// Set group header rows to span all columns
542setFirstColumnSpanned(i, parent, true);
543}
544if (child->isExpanded()) {
545setExpanded(index, true);
546}
547}
548
549if (parent.isValid()) {
550setEditorMode(parent, start, end);
551}
552}
553
554void PropertyEditor::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
555{
556QTreeView::rowsAboutToBeRemoved(parent, start, end);
557
558auto item = static_cast<PropertyItem*>(parent.internalPointer());
559if (item && item->isSeparator() && item->childCount() == end - start + 1) {
560setRowHidden(parent.row(), propertyModel->parent(parent), true);
561}
562
563if (editingIndex.isValid()) {
564if (editingIndex.row() >= start && editingIndex.row() <= end) {
565closeTransaction();
566}
567else {
568removingRows = 1;
569for (QWidget* w = qApp->focusWidget(); w; w = w->parentWidget()) {
570if (w == activeEditor) {
571removingRows = -1;
572break;
573}
574}
575}
576}
577}
578
579void PropertyEditor::onRowsRemoved(const QModelIndex&, int, int)
580{
581if (removingRows < 0 && activeEditor) {
582activeEditor->setFocus();
583}
584removingRows = 0;
585}
586
587void PropertyEditor::drawBranches(QPainter* painter,
588const QRect& rect,
589const QModelIndex& index) const
590{
591QTreeView::drawBranches(painter, rect, index);
592
593auto property = static_cast<PropertyItem*>(index.internalPointer());
594
595if (property && property->isSeparator()) {
596painter->fillRect(rect, this->background);
597}
598}
599
600void Gui::PropertyEditor::PropertyEditor::drawRow(QPainter* painter,
601const QStyleOptionViewItem& options,
602const QModelIndex& index) const
603{
604// render background also for non alternate rows based on the `itemBackground` property.
605painter->fillRect(options.rect, itemBackground());
606
607QTreeView::drawRow(painter, options, index);
608}
609
610void PropertyEditor::buildUp(PropertyModel::PropertyList&& props, bool _checkDocument)
611{
612checkDocument = _checkDocument;
613
614if (committing) {
615Base::Console().Warning(
616"While committing the data to the property the selection has changed.\n");
617delaybuild = true;
618return;
619}
620
621// Do not close transaction here, because we are now doing incremental
622// update in PropertyModel::buildUp()
623//
624// closeTransaction();
625
626QModelIndex index = this->currentIndex();
627QStringList propertyPath = propertyModel->propertyPathFromIndex(index);
628if (!propertyPath.isEmpty()) {
629this->selectedProperty = propertyPath;
630}
631propertyModel->buildUp(props);
632if (!this->selectedProperty.isEmpty()) {
633QModelIndex index = propertyModel->propertyIndexFromPath(this->selectedProperty);
634this->setCurrentIndex(index);
635}
636
637propList = std::move(props);
638propOwners.clear();
639for (auto& v : propList) {
640for (auto prop : v.second) {
641auto container = prop->getContainer();
642if (!container) {
643continue;
644}
645// Include document to get proper handling in PropertyView::slotDeleteDocument()
646if (checkDocument && container->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
647propOwners.insert(static_cast<App::DocumentObject*>(container)->getDocument());
648}
649propOwners.insert(container);
650}
651}
652
653if (autoexpand) {
654expandAll();
655}
656}
657
658void PropertyEditor::updateProperty(const App::Property& prop)
659{
660// forward this to the model if the property is changed from outside
661if (!committing) {
662propertyModel->updateProperty(prop);
663}
664}
665
666void PropertyEditor::setEditorMode(const QModelIndex& parent, int start, int end)
667{
668int column = 1;
669for (int i = start; i <= end; i++) {
670QModelIndex item = propertyModel->index(i, column, parent);
671auto propItem = static_cast<PropertyItem*>(item.internalPointer());
672if (!PropertyView::showAll() && propItem && propItem->testStatus(App::Property::Hidden)) {
673setRowHidden(i, parent, true);
674}
675}
676}
677
678void PropertyEditor::removeProperty(const App::Property& prop)
679{
680for (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
682std::vector<App::Property*>::iterator pos =
683std::find(it->second.begin(), it->second.end(), &prop);
684if (pos != it->second.end()) {
685it->second.erase(pos);
686// if the last property of this name is removed then also remove the whole group
687if (it->second.empty()) {
688propList.erase(it);
689}
690propertyModel->removeProperty(prop);
691break;
692}
693}
694}
695
696enum MenuAction
697{
698MA_AutoExpand,
699MA_ShowHidden,
700MA_Expression,
701MA_RemoveProp,
702MA_AddProp,
703MA_EditPropGroup,
704MA_Transient,
705MA_Output,
706MA_NoRecompute,
707MA_ReadOnly,
708MA_Hidden,
709MA_Touched,
710MA_EvalOnRestore,
711MA_CopyOnChange,
712};
713
714void PropertyEditor::contextMenuEvent(QContextMenuEvent*)
715{
716QMenu menu;
717QAction* autoExpand = nullptr;
718
719auto contextIndex = currentIndex();
720
721// acquiring the selected properties
722std::unordered_set<App::Property*> props;
723const auto indexes = selectedIndexes();
724for (const auto& index : indexes) {
725auto item = static_cast<PropertyItem*>(index.internalPointer());
726if (item->isSeparator()) {
727continue;
728}
729for (auto parent = item; parent; parent = parent->parent()) {
730const auto& ps = parent->getPropertyData();
731if (!ps.empty()) {
732props.insert(ps.begin(), ps.end());
733break;
734}
735}
736}
737
738// add property
739menu.addAction(tr("Add property"))->setData(QVariant(MA_AddProp));
740if (!props.empty() && std::all_of(props.begin(), props.end(), [](auto prop) {
741return prop->testStatus(App::Property::PropDynamic)
742&& !boost::starts_with(prop->getName(), prop->getGroup());
743})) {
744menu.addAction(tr("Rename property group"))->setData(QVariant(MA_EditPropGroup));
745}
746
747// remove property
748bool canRemove = !props.empty();
749unsigned long propType = 0;
750unsigned long propStatus = 0xffffffff;
751for (auto prop : props) {
752propType |= prop->getType();
753propStatus &= prop->getStatus();
754if (!prop->testStatus(App::Property::PropDynamic)
755|| prop->testStatus(App::Property::LockDynamic)) {
756canRemove = false;
757}
758}
759if (canRemove) {
760menu.addAction(tr("Remove property"))->setData(QVariant(MA_RemoveProp));
761}
762
763// add a separator between adding/removing properties and the rest
764menu.addSeparator();
765
766// show all
767QAction* showHidden = menu.addAction(tr("Show hidden"));
768showHidden->setCheckable(true);
769showHidden->setChecked(PropertyView::showAll());
770showHidden->setData(QVariant(MA_ShowHidden));
771
772// auto expand
773autoExpand = menu.addAction(tr("Auto expand"));
774autoExpand->setCheckable(true);
775autoExpand->setChecked(autoexpand);
776autoExpand->setData(QVariant(MA_AutoExpand));
777
778// expression
779if (props.size() == 1) {
780auto item = static_cast<PropertyItem*>(contextIndex.internalPointer());
781auto prop = *props.begin();
782if (item->isBound() && !prop->isDerivedFrom(App::PropertyExpressionEngine::getClassTypeId())
783&& !prop->isReadOnly() && !prop->testStatus(App::Property::Immutable)
784&& !(prop->getType() & App::Prop_ReadOnly)) {
785contextIndex = propertyModel->buddy(contextIndex);
786setCurrentIndex(contextIndex);
787// menu.addSeparator();
788menu.addAction(tr("Expression..."))->setData(QVariant(MA_Expression));
789}
790}
791
792// the various flags
793if (!props.empty()) {
794menu.addSeparator();
795
796// the subMenu is allocated on the heap but managed by menu.
797auto subMenu = new QMenu(QString::fromLatin1("Status"), &menu);
798
799QAction* action;
800QString text;
801#define _ACTION_SETUP(_name) \
802do { \
803text = tr(#_name); \
804action = subMenu->addAction(text); \
805action->setData(QVariant(MA_##_name)); \
806action->setCheckable(true); \
807if (propStatus & (1 << App::Property::_name)) \
808action->setChecked(true); \
809} while (0)
810#define ACTION_SETUP(_name) \
811do { \
812_ACTION_SETUP(_name); \
813if (propType & App::Prop_##_name) { \
814action->setText(text + QString::fromLatin1(" *")); \
815action->setChecked(true); \
816} \
817} while (0)
818
819ACTION_SETUP(Hidden);
820ACTION_SETUP(Output);
821ACTION_SETUP(NoRecompute);
822ACTION_SETUP(ReadOnly);
823ACTION_SETUP(Transient);
824_ACTION_SETUP(Touched);
825_ACTION_SETUP(EvalOnRestore);
826_ACTION_SETUP(CopyOnChange);
827
828menu.addMenu(subMenu);
829}
830
831auto action = menu.exec(QCursor::pos());
832if (!action) {
833return;
834}
835
836switch (action->data().toInt()) {
837case MA_AutoExpand:
838if (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.
842autoexpand = autoExpand->isChecked();
843if (autoexpand) {
844expandAll();
845}
846}
847return;
848case MA_ShowHidden:
849PropertyView::setShowAll(action->isChecked());
850return;
851#define ACTION_CHECK(_name) \
852case MA_##_name: \
853for (auto prop : props) \
854prop->setStatus(App::Property::_name, action->isChecked()); \
855break
856ACTION_CHECK(Transient);
857ACTION_CHECK(ReadOnly);
858ACTION_CHECK(Output);
859ACTION_CHECK(Hidden);
860ACTION_CHECK(EvalOnRestore);
861ACTION_CHECK(CopyOnChange);
862case MA_Touched:
863for (auto prop : props) {
864if (action->isChecked()) {
865prop->touch();
866}
867else {
868prop->purgeTouched();
869}
870}
871break;
872case MA_Expression:
873if (contextIndex == currentIndex()) {
874Base::FlagToggler<> flag(binding);
875closeEditor();
876openEditor(contextIndex);
877}
878break;
879case MA_AddProp: {
880App::AutoTransaction committer("Add property");
881std::unordered_set<App::PropertyContainer*> containers;
882auto sels = Gui::Selection().getSelection("*");
883if (sels.size() == 1) {
884containers.insert(sels[0].pObject);
885}
886else {
887for (auto prop : props) {
888containers.insert(prop->getContainer());
889}
890}
891Gui::Dialog::DlgAddProperty dlg(Gui::getMainWindow(), std::move(containers));
892dlg.exec();
893return;
894}
895case MA_EditPropGroup: {
896// This operation is not undoable yet.
897const char* groupName = (*props.begin())->getGroup();
898if (!groupName) {
899groupName = "Base";
900}
901QString res = QInputDialog::getText(Gui::getMainWindow(),
902tr("Rename property group"),
903tr("Group name:"),
904QLineEdit::Normal,
905QString::fromUtf8(groupName));
906if (res.size()) {
907std::string group = res.toUtf8().constData();
908for (auto prop : props) {
909prop->getContainer()->changeDynamicProperty(prop, group.c_str(), nullptr);
910}
911buildUp(PropertyModel::PropertyList(propList), checkDocument);
912}
913return;
914}
915case MA_RemoveProp: {
916App::AutoTransaction committer("Remove property");
917for (auto prop : props) {
918try {
919prop->getContainer()->removeDynamicProperty(prop->getName());
920}
921catch (Base::Exception& e) {
922e.ReportException();
923}
924}
925break;
926}
927default:
928break;
929}
930}
931
932
933bool PropertyEditor::eventFilter(QObject* object, QEvent* event)
934{
935if (object == viewport()) {
936QMouseEvent* mouse_event = dynamic_cast<QMouseEvent*>(event);
937if (mouse_event) {
938if (mouse_event->type() == QEvent::MouseMove) {
939if (dragInProgress) { // apply dragging
940QHeaderView* header_view = header();
941int delta = mouse_event->pos().x() - dragPreviousPos;
942dragPreviousPos = mouse_event->pos().x();
943// using minimal size = dragSensibility * 2 to prevent collapsing
944header_view->resizeSection(
945dragSection,
946qMax(dragSensibility * 2, header_view->sectionSize(dragSection) + delta));
947return true;
948}
949else { // set mouse cursor shape
950if (indexResizable(mouse_event->pos()).isValid()) {
951viewport()->setCursor(Qt::SplitHCursor);
952}
953else {
954viewport()->setCursor(QCursor());
955}
956}
957}
958else if (mouse_event->type() == QEvent::MouseButtonPress
959&& mouse_event->button() == Qt::LeftButton && !dragInProgress) {
960if (indexResizable(mouse_event->pos()).isValid()) {
961dragInProgress = true;
962#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
963dragPreviousPos = mouse_event->x();
964#else
965dragPreviousPos = mouse_event->position().toPoint().x();
966#endif
967dragSection = indexResizable(mouse_event->pos()).column();
968return true;
969}
970}
971else if (mouse_event->type() == QEvent::MouseButtonRelease
972&& mouse_event->button() == Qt::LeftButton && dragInProgress) {
973dragInProgress = false;
974
975auto hGrp = App::GetApplication().GetParameterGroupByPath(
976"User parameter:BaseApp/Preferences/DockWindows/PropertyView");
977hGrp->SetInt("FirstColumnSize", header()->sectionSize(0));
978return true;
979}
980}
981}
982return false;
983}
984
985QModelIndex PropertyEditor::indexResizable(QPoint mouse_pos)
986{
987QModelIndex index = indexAt(mouse_pos - QPoint(dragSensibility + 1, 0));
988if (index.isValid()) {
989if (qAbs(visualRect(index).right() - mouse_pos.x()) < dragSensibility
990&& header()->sectionResizeMode(index.column()) == QHeaderView::Interactive) {
991return index;
992}
993}
994return QModelIndex();
995}
996
997#include "moc_PropertyEditor.cpp"
998