1
/***************************************************************************
2
* Copyright (c) 2023 David Friedli <david[at]friedli-be.ch> *
4
* This file is part of FreeCAD. *
6
* FreeCAD is free software: you can redistribute it and/or modify it *
7
* under the terms of the GNU Lesser General Public License as *
8
* published by the Free Software Foundation, either version 2.1 of the *
9
* License, or (at your option) any later version. *
11
* FreeCAD is distributed in the hope that it will be useful, but *
12
* WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14
* Lesser General Public License for more details. *
16
* You should have received a copy of the GNU Lesser General Public *
17
* License along with FreeCAD. If not, see *
18
* <https://www.gnu.org/licenses/>. *
20
**************************************************************************/
23
#include "PreCompiled.h"
26
#include <QApplication>
31
#include "TaskMeasure.h"
33
#include <App/DocumentObjectGroup.h>
35
#include <Mod/Measure/App/MeasureDistance.h>
36
#include <App/PropertyStandard.h>
37
#include <Gui/MainWindow.h>
38
#include <Gui/Application.h>
39
#include <Gui/BitmapFactory.h>
40
#include <Gui/Control.h>
41
#include <Gui/ViewProviderDocumentObject.h>
51
constexpr auto taskMeasureSettingsGroup = "TaskMeasure";
52
constexpr auto taskMeasureShowDeltaSettingsName = "ShowDelta";
55
TaskMeasure::TaskMeasure()
57
qApp->installEventFilter(this);
59
this->setButtonPosition(TaskMeasure::South);
60
auto taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("umf-measurement"),
66
settings.beginGroup(QLatin1String(taskMeasureSettingsGroup));
67
delta = settings.value(QLatin1String(taskMeasureShowDeltaSettingsName), true).toBool();
69
showDelta = new QCheckBox();
70
showDelta->setChecked(delta);
71
showDeltaLabel = new QLabel(tr("Show Delta:"));
72
connect(showDelta, &QCheckBox::stateChanged, this, &TaskMeasure::showDeltaChanged);
74
// Create mode dropdown and add all registered measuretypes
75
modeSwitch = new QComboBox();
76
modeSwitch->addItem(QString::fromLatin1("Auto"));
78
for (App::MeasureType* mType : App::MeasureManager::getMeasureTypes()) {
79
modeSwitch->addItem(QString::fromLatin1(mType->label.c_str()));
82
// Connect dropdown's change signal to our onModeChange slot
84
qOverload<int>(&QComboBox::currentIndexChanged),
86
&TaskMeasure::onModeChanged);
89
valueResult = new QLineEdit();
90
valueResult->setReadOnly(true);
93
QBoxLayout* layout = taskbox->groupLayout();
95
QFormLayout* formLayout = new QFormLayout();
96
formLayout->setHorizontalSpacing(10);
97
// Note: How can the split between columns be kept in the middle?
98
// formLayout->setFieldGrowthPolicy(QFormLayout::FieldGrowthPolicy::ExpandingFieldsGrow);
99
formLayout->setFormAlignment(Qt::AlignCenter);
101
formLayout->addRow(tr("Mode:"), modeSwitch);
102
formLayout->addRow(showDeltaLabel, showDelta);
103
formLayout->addRow(tr("Result:"), valueResult);
104
layout->addLayout(formLayout);
106
Content.emplace_back(taskbox);
108
// engage the selectionObserver
111
// Set selection style
112
Gui::Selection().setSelectionStyle(Gui::SelectionSingleton::SelectionStyle::GreedySelection);
114
if (!App::GetApplication().getActiveTransaction()) {
115
App::GetApplication().setActiveTransaction("Add Measurement");
119
// Call invoke method delayed, otherwise the dialog might not be fully initialized
120
QTimer::singleShot(0, this, &TaskMeasure::invoke);
123
TaskMeasure::~TaskMeasure()
125
Gui::Selection().setSelectionStyle(Gui::SelectionSingleton::SelectionStyle::NormalSelection);
127
qApp->removeEventFilter(this);
131
void TaskMeasure::modifyStandardButtons(QDialogButtonBox* box)
134
QPushButton* btn = box->button(QDialogButtonBox::Apply);
135
btn->setText(tr("Save"));
136
btn->setToolTip(tr("Save the measurement in the active document."));
137
connect(btn, &QPushButton::released, this, &TaskMeasure::apply);
139
// Disable button by default
140
btn->setEnabled(false);
141
btn = box->button(QDialogButtonBox::Abort);
142
btn->setText(tr("Close"));
143
btn->setToolTip(tr("Close the measurement task."));
145
// Connect reset button
146
btn = box->button(QDialogButtonBox::Reset);
147
connect(btn, &QPushButton::released, this, &TaskMeasure::reset);
150
bool canAnnotate(Measure::MeasureBase* obj)
152
if (obj == nullptr) {
153
// null object, can't annotate this
157
auto vpName = obj->getViewProviderName();
158
// if there is not a vp, return false
159
if ((vpName == nullptr) || (vpName[0] == '\0')) {
166
void TaskMeasure::enableAnnotateButton(bool state)
168
// if the task ui is not init yet we don't have a button box.
169
if (!this->buttonBox) {
172
// Enable/Disable annotate button
173
auto btn = this->buttonBox->button(QDialogButtonBox::Apply);
174
btn->setEnabled(state);
177
void TaskMeasure::setMeasureObject(Measure::MeasureBase* obj)
179
_mMeasureObject = obj;
183
App::DocumentObject* TaskMeasure::createObject(const App::MeasureType* measureType)
186
measureType->isPython ? "Measure::MeasurePython" : measureType->measureObject;
187
auto type = Base::Type::getTypeIfDerivedFrom(measureClass.c_str(),
188
App::DocumentObject::getClassTypeId(),
195
_mMeasureObject = static_cast<Measure::MeasureBase*>(type.createInstance());
197
// Create an instance of the python measure class, the classe's
198
// initializer sets the object as proxy
199
if (measureType->isPython) {
200
Base::PyGILStateLocker lock;
202
args.setItem(0, Py::asObject(_mMeasureObject->getPyObject()));
203
PyObject* result = PyObject_CallObject(measureType->pythonClass, args.ptr());
207
return static_cast<App::DocumentObject*>(_mMeasureObject);
211
Gui::ViewProviderDocumentObject* TaskMeasure::createViewObject(App::DocumentObject* measureObj)
214
auto vpName = measureObj->getViewProviderName();
215
if ((vpName == nullptr) || (vpName[0] == '\0')) {
220
Base::Type::getTypeIfDerivedFrom(vpName,
221
Gui::ViewProviderDocumentObject::getClassTypeId(),
223
if (vpType.isBad()) {
227
auto vp = static_cast<Gui::ViewProviderDocumentObject*>(vpType.createInstance());
229
_mGuiDocument = Gui::Application::Instance->activeDocument();
230
_mGuiDocument->setAnnotationViewProvider(vp->getTypeId().getName(), vp);
231
vp->attach(measureObj);
233
// Init the position of the annotation
234
static_cast<MeasureGui::ViewProviderMeasureBase*>(vp)->positionAnno(_mMeasureObject);
244
void TaskMeasure::saveObject()
246
if (_mViewObject && _mGuiDocument) {
247
_mGuiDocument->addViewProvider(_mViewObject);
248
_mGuiDocument->takeAnnotationViewProvider(_mViewObject->getTypeId().getName());
249
_mViewObject = nullptr;
252
_mDocument = App::GetApplication().getActiveDocument();
253
_mDocument->addObject(_mMeasureObject, _mMeasureType->label.c_str());
257
void TaskMeasure::update()
259
App::Document* doc = App::GetApplication().getActiveDocument();
261
// Reset selection if the selected object is not valid
262
for (auto sel : Gui::Selection().getSelection()) {
263
App::DocumentObject* ob = sel.pObject;
264
App::DocumentObject* sub = ob->getSubObject(sel.SubName);
267
if (sub->isDerivedFrom<App::Link>()) {
268
auto link = static_cast<App::Link*>(sub);
269
sub = link->getLinkedObject(true);
272
std::string mod = Base::Type::getModuleName(sub->getTypeId().getName());
273
if (!App::MeasureManager::hasMeasureHandler(mod.c_str())) {
274
Base::Console().Message("No measure handler available for geometry of module: %s\n",
281
valueResult->setText(QString::asprintf("-"));
283
// Get valid measure type
285
std::string mode = explicitMode ? modeSwitch->currentText().toStdString() : "";
287
App::MeasureSelection selection;
288
for (auto s : Gui::Selection().getSelection(doc->getName(), ResolveMode::NoResolve)) {
289
App::SubObjectT sub(s.pObject, s.SubName);
291
App::MeasureSelectionItem item = {sub, Base::Vector3d(s.x, s.y, s.z)};
292
selection.push_back(item);
295
auto measureTypes = App::MeasureManager::getValidMeasureTypes(selection, mode);
296
if (measureTypes.size() > 0) {
297
_mMeasureType = measureTypes.front();
301
if (!_mMeasureType) {
303
// Note: If there's no valid measure type we might just restart the selection,
304
// however this requires enough coverage of measuretypes that we can access all of them
306
// std::tuple<std::string, std::string> sel = selection.back();
308
// addElement(measureModule.c_str(), get<0>(sel).c_str(), get<1>(sel).c_str());
310
// Reset measure object
312
setModeSilent(nullptr);
315
enableAnnotateButton(false);
319
// Update tool mode display
320
setModeSilent(_mMeasureType);
323
|| _mMeasureType->measureObject != _mMeasureObject->getTypeId().getName()) {
324
// we don't already have a measureobject or it isn't the same type as the new one
326
createObject(_mMeasureType);
329
// we have a valid measure object so we can enable the annotate button
330
enableAnnotateButton(true);
332
// Fill measure object's properties from selection
333
_mMeasureObject->parseSelection(selection);
336
valueResult->setText(_mMeasureObject->getResultString());
338
createViewObject(_mMeasureObject);
340
// Must be after createViewObject!
341
assert(_mViewObject);
342
auto* prop = dynamic_cast<App::PropertyBool*>(_mViewObject->getPropertyByName("ShowDelta"));
343
setDeltaPossible(prop != nullptr);
345
prop->setValue(showDelta->isChecked());
346
_mViewObject->update(prop);
350
void TaskMeasure::close()
352
Control().closeDialog();
356
void TaskMeasure::ensureGroup(Measure::MeasureBase* measurement)
358
// Ensure measurement object is part of the measurements group
360
const char* measurementGroupName = "Measurements";
361
if (measurement == nullptr) {
365
App::Document* doc = measurement->getDocument();
366
App::DocumentObject* obj = doc->getObject(measurementGroupName);
367
if (!obj || !obj->isValid()) {
368
obj = doc->addObject("App::DocumentObjectGroup",
369
measurementGroupName,
371
"MeasureGui::ViewProviderMeasureGroup");
374
auto group = static_cast<App::DocumentObjectGroup*>(obj);
375
group->addObject(measurement);
379
// Runs after the dialog is created
380
void TaskMeasure::invoke()
385
bool TaskMeasure::apply()
388
ensureGroup(_mMeasureObject);
391
// Commit transaction
392
App::GetApplication().closeActiveTransaction();
393
App::GetApplication().setActiveTransaction("Add Measurement");
397
bool TaskMeasure::reject()
403
App::GetApplication().closeActiveTransaction(true);
407
void TaskMeasure::reset()
410
_mMeasureType = nullptr;
411
_mMeasureObject = nullptr;
412
this->clearSelection();
414
// Should the explicit mode also be reset?
415
// setModeSilent(nullptr);
416
// explicitMode = false;
422
void TaskMeasure::removeObject()
424
if (_mMeasureObject == nullptr) {
427
if (_mMeasureObject->isRemoving()) {
431
if (_mViewObject && _mGuiDocument) {
432
_mGuiDocument->removeAnnotationViewProvider(_mViewObject->getTypeId().getName());
433
_mViewObject = nullptr;
436
delete _mMeasureObject;
437
setMeasureObject(nullptr);
440
bool TaskMeasure::hasSelection()
442
return !Gui::Selection().getSelection().empty();
445
void TaskMeasure::clearSelection()
447
Gui::Selection().clearSelection();
450
void TaskMeasure::onSelectionChanged(const Gui::SelectionChanges& msg)
452
// Skip non-relevant events
453
if (msg.Type != SelectionChanges::AddSelection && msg.Type != SelectionChanges::RmvSelection
454
&& msg.Type != SelectionChanges::SetSelection
455
&& msg.Type != SelectionChanges::ClrSelection) {
463
bool TaskMeasure::eventFilter(QObject* obj, QEvent* event)
466
if (event->type() == QEvent::KeyPress) {
467
auto keyEvent = static_cast<QKeyEvent*>(event);
469
if (keyEvent->key() == Qt::Key_Escape) {
471
if (this->hasSelection()) {
481
if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
482
// Save object. Indirectly dependent on whether the apply button is enabled
483
// enabled if valid measurement object.
484
this->buttonBox->button(QDialogButtonBox::Apply)->click();
489
return TaskDialog::eventFilter(obj, event);
492
void TaskMeasure::setDeltaPossible(bool possible)
494
showDelta->setVisible(possible);
495
showDeltaLabel->setVisible(possible);
498
void TaskMeasure::onModeChanged(int index)
500
explicitMode = (index != 0);
505
void TaskMeasure::showDeltaChanged(int checkState)
507
delta = checkState == Qt::CheckState::Checked;
510
settings.beginGroup(QLatin1String(taskMeasureSettingsGroup));
511
settings.setValue(QLatin1String(taskMeasureShowDeltaSettingsName), delta);
516
void TaskMeasure::setModeSilent(App::MeasureType* mode)
518
modeSwitch->blockSignals(true);
520
if (mode == nullptr) {
521
modeSwitch->setCurrentIndex(0);
524
modeSwitch->setCurrentText(QString::fromLatin1(mode->label.c_str()));
526
modeSwitch->blockSignals(false);
529
// Get explicitly set measure type from the mode switch
530
App::MeasureType* TaskMeasure::getMeasureType()
532
for (App::MeasureType* mType : App::MeasureManager::getMeasureTypes()) {
533
if (mType->label.c_str() == modeSwitch->currentText().toLatin1()) {