| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | #include <QApplication> |
| | #include <QKeyEvent> |
| |
|
| |
|
| | #include "TaskMeasure.h" |
| |
|
| | #include <App/DocumentObjectGroup.h> |
| | #include <App/Link.h> |
| | #include <Mod/Measure/App/MeasureDistance.h> |
| | #include <App/PropertyStandard.h> |
| | #include <Gui/MainWindow.h> |
| | #include <Gui/Application.h> |
| | #include <Gui/BitmapFactory.h> |
| | #include <Gui/Control.h> |
| | #include <Gui/ViewProvider.h> |
| |
|
| | #include <QFormLayout> |
| | #include <QVBoxLayout> |
| | #include <QPushButton> |
| | #include <QSettings> |
| | #include <QAction> |
| | #include <QMenu> |
| | #include <QShortcut> |
| | #include <QToolTip> |
| |
|
| | using namespace MeasureGui; |
| |
|
| | namespace |
| | { |
| | constexpr auto taskMeasureSettingsGroup = "TaskMeasure"; |
| | constexpr auto taskMeasureShowDeltaSettingsName = "ShowDelta"; |
| | constexpr auto taskMeasureAutoSaveSettingsName = "AutoSave"; |
| | constexpr auto taskMeasureGreedySelection = "GreedySelection"; |
| |
|
| | using SelectionStyle = Gui::SelectionSingleton::SelectionStyle; |
| | } |
| |
|
| | TaskMeasure::TaskMeasure() |
| | { |
| | this->setButtonPosition(TaskMeasure::South); |
| | auto taskbox = new Gui::TaskView::TaskBox( |
| | Gui::BitmapFactory().pixmap("umf-measurement"), |
| | tr("Measurement"), |
| | true, |
| | nullptr |
| | ); |
| |
|
| | setupShortcuts(taskbox); |
| |
|
| | QSettings settings; |
| | settings.beginGroup(QLatin1String(taskMeasureSettingsGroup)); |
| | delta = settings.value(QLatin1String(taskMeasureShowDeltaSettingsName), true).toBool(); |
| | mAutoSave = settings.value(QLatin1String(taskMeasureAutoSaveSettingsName), mAutoSave).toBool(); |
| | if (settings.value(QLatin1String(taskMeasureGreedySelection), false).toBool()) { |
| | Gui::Selection().setSelectionStyle(SelectionStyle::GreedySelection); |
| | } |
| | else { |
| | Gui::Selection().setSelectionStyle(SelectionStyle::NormalSelection); |
| | } |
| | settings.endGroup(); |
| |
|
| | showDelta = new QCheckBox(); |
| | showDelta->setChecked(delta); |
| | showDeltaLabel = new QLabel(tr("Show Delta:")); |
| | #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) |
| | connect(showDelta, &QCheckBox::checkStateChanged, this, &TaskMeasure::showDeltaChanged); |
| | #else |
| | connect(showDelta, &QCheckBox::stateChanged, this, &TaskMeasure::showDeltaChanged); |
| | #endif |
| | autoSaveAction = new QAction(tr("Auto Save")); |
| | autoSaveAction->setCheckable(true); |
| | autoSaveAction->setChecked(mAutoSave); |
| | autoSaveAction->setToolTip( |
| | tr("Auto saving of the last measurement when starting a new " |
| | "measurement. Use the Shift key to temporarily invert the behaviour.") |
| | ); |
| | connect(autoSaveAction, &QAction::triggered, this, &TaskMeasure::autoSaveChanged); |
| |
|
| | newMeasurementBehaviourAction = new QAction(tr("Additive Selection")); |
| | newMeasurementBehaviourAction->setCheckable(true); |
| | newMeasurementBehaviourAction->setChecked( |
| | Gui::Selection().getSelectionStyle() == SelectionStyle::GreedySelection |
| | ); |
| | newMeasurementBehaviourAction->setToolTip( |
| | tr("If checked, new selection will be added to the measurement. If unchecked, the Ctrl key " |
| | "must be " |
| | "pressed to add a " |
| | "selection to the current measurement otherwise a new measurement will be started") |
| | ); |
| | connect( |
| | newMeasurementBehaviourAction, |
| | &QAction::triggered, |
| | this, |
| | &TaskMeasure::newMeasurementBehaviourChanged |
| | ); |
| |
|
| | mSettings = new QToolButton(); |
| | mSettings->setToolTip(tr("Settings")); |
| | mSettings->setIcon(QIcon(QStringLiteral(":/icons/dialogs/Sketcher_Settings.svg"))); |
| | auto* menu = new QMenu(mSettings); |
| | menu->setToolTipsVisible(true); |
| | mSettings->setMenu(menu); |
| |
|
| | menu->addAction(autoSaveAction); |
| | menu->addAction(newMeasurementBehaviourAction); |
| | connect(mSettings, &QToolButton::clicked, mSettings, &QToolButton::showMenu); |
| |
|
| | |
| | modeSwitch = new QComboBox(); |
| | modeSwitch->addItem(tr("Auto")); |
| |
|
| | for (App::MeasureType* mType : App::MeasureManager::getMeasureTypes()) { |
| | modeSwitch->addItem(tr(mType->label.c_str())); |
| | } |
| |
|
| | |
| | connect(modeSwitch, qOverload<int>(&QComboBox::currentIndexChanged), this, &TaskMeasure::onModeChanged); |
| |
|
| | |
| | valueResult = new QLineEdit(); |
| | valueResult->setReadOnly(true); |
| |
|
| | |
| | QBoxLayout* layout = taskbox->groupLayout(); |
| |
|
| | QFormLayout* formLayout = new QFormLayout(); |
| | formLayout->setHorizontalSpacing(10); |
| | |
| | |
| | formLayout->setFormAlignment(Qt::AlignCenter); |
| |
|
| | auto* settingsLayout = new QHBoxLayout(); |
| | settingsLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); |
| | settingsLayout->addWidget(mSettings); |
| | formLayout->addRow(QLatin1String(), settingsLayout); |
| | formLayout->addRow(tr("Mode:"), modeSwitch); |
| | formLayout->addRow(showDeltaLabel, showDelta); |
| | formLayout->addRow(tr("Result:"), valueResult); |
| | layout->addLayout(formLayout); |
| |
|
| | Content.emplace_back(taskbox); |
| |
|
| | |
| | attachSelection(); |
| |
|
| | if (auto* doc = App::GetApplication().getActiveDocument()) { |
| | m_deletedConnection = doc->signalDeletedObject.connect([this](auto&& obj) { |
| | onObjectDeleted(obj); |
| | }); |
| | } |
| |
|
| | if (!App::GetApplication().getActiveTransaction()) { |
| | App::GetApplication().setActiveTransaction("Add Measurement"); |
| | } |
| |
|
| | setAutoCloseOnDeletedDocument(true); |
| | |
| | QTimer::singleShot(0, this, &TaskMeasure::invoke); |
| | } |
| |
|
| | TaskMeasure::~TaskMeasure() |
| | { |
| | m_deletedConnection.disconnect(); |
| | Gui::Selection().setSelectionStyle(SelectionStyle::NormalSelection); |
| | detachSelection(); |
| | } |
| |
|
| | void TaskMeasure::modifyStandardButtons(QDialogButtonBox* box) |
| | { |
| |
|
| | QPushButton* btn = box->button(QDialogButtonBox::Apply); |
| | btn->setText(QCoreApplication::translate("QPlatformTheme", "Save")); |
| | btn->setToolTip(tr("Saves the measurement in the active document")); |
| | connect(btn, &QPushButton::released, this, qOverload<>(&TaskMeasure::apply)); |
| |
|
| | |
| | btn->setEnabled(false); |
| | btn = box->button(QDialogButtonBox::Abort); |
| | btn->setText(tr("Close")); |
| | btn->setToolTip(tr("Close the measurement task.")); |
| |
|
| | |
| | btn = box->button(QDialogButtonBox::Reset); |
| | connect(btn, &QPushButton::released, this, &TaskMeasure::reset); |
| | } |
| |
|
| |
|
| | void TaskMeasure::enableAnnotateButton(bool state) |
| | { |
| | |
| | if (!this->buttonBox) { |
| | return; |
| | } |
| | |
| | auto btn = this->buttonBox->button(QDialogButtonBox::Apply); |
| | btn->setEnabled(state); |
| | } |
| |
|
| |
|
| | void TaskMeasure::createObject(const App::MeasureType* measureType) |
| | { |
| | App::Document* doc = App::GetApplication().getActiveDocument(); |
| | if (!doc) { |
| | return; |
| | } |
| |
|
| | if (measureType->isPython) { |
| | Base::PyGILStateLocker lock; |
| | auto pyMeasureClass = measureType->pythonClass; |
| |
|
| | |
| | |
| | |
| | |
| | auto featurePython = doc->addObject("Measure::MeasurePython", measureType->label.c_str()); |
| | _mMeasureObject = dynamic_cast<Measure::MeasureBase*>(featurePython); |
| |
|
| | |
| | |
| | Py::Tuple args(1); |
| | args.setItem(0, Py::asObject(_mMeasureObject->getPyObject())); |
| | PyObject* result = PyObject_CallObject(pyMeasureClass, args.ptr()); |
| | Py_XDECREF(result); |
| | } |
| | else { |
| | |
| | _mMeasureObject = dynamic_cast<Measure::MeasureBase*>( |
| | doc->addObject(measureType->measureObject.c_str(), measureType->label.c_str()) |
| | ); |
| | } |
| | } |
| |
|
| | void TaskMeasure::update() |
| | { |
| | try { |
| | tryUpdate(); |
| | } |
| | catch (const Base::Exception& e) { |
| | e.reportException(); |
| | } |
| | } |
| |
|
| | void TaskMeasure::tryUpdate() |
| | { |
| | App::Document* doc = App::GetApplication().getActiveDocument(); |
| |
|
| | |
| | for (auto sel : Gui::Selection().getSelection()) { |
| | App::DocumentObject* ob = sel.pObject; |
| | App::DocumentObject* sub = ob->getSubObject(sel.SubName); |
| |
|
| | |
| | if (auto link = freecad_cast<App::Link*>(sub)) { |
| | sub = link->getLinkedObject(true); |
| | } |
| |
|
| | std::string mod = Base::Type::getModuleName(sub->getTypeId().getName()); |
| | if (!App::MeasureManager::hasMeasureHandler(mod.c_str())) { |
| | Base::Console().message("No measure handler available for geometry of module: %s\n", mod); |
| | clearSelection(); |
| | return; |
| | } |
| | } |
| |
|
| | valueResult->setText(QString::asprintf("-")); |
| |
|
| | std::string mode = explicitMode ? modeSwitch->currentText().toStdString() : ""; |
| |
|
| | App::MeasureSelection selection; |
| | for (auto s : Gui::Selection().getSelection(doc->getName(), Gui::ResolveMode::NoResolve)) { |
| | App::SubObjectT sub(s.pObject, s.SubName); |
| |
|
| | App::MeasureSelectionItem item = {sub, Base::Vector3d(s.x, s.y, s.z)}; |
| | selection.push_back(item); |
| | } |
| |
|
| | |
| | App::MeasureType* measureType = nullptr; |
| | auto measureTypes = App::MeasureManager::getValidMeasureTypes(selection, mode); |
| | if (!measureTypes.empty()) { |
| | measureType = measureTypes.front(); |
| | } |
| |
|
| |
|
| | if (!measureType) { |
| |
|
| | |
| | |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | if (!explicitMode) { |
| | setModeSilent(nullptr); |
| | } |
| | removeObject(); |
| | enableAnnotateButton(false); |
| | return; |
| | } |
| |
|
| | |
| | setModeSilent(measureType); |
| |
|
| | if (!_mMeasureObject || measureType->measureObject != _mMeasureObject->getTypeId().getName() |
| | || _mMeasureObject->getDocument() != doc) { |
| | |
| | removeObject(); |
| | createObject(measureType); |
| | } |
| |
|
| | |
| | enableAnnotateButton(true); |
| |
|
| | if (_mMeasureObject) { |
| | |
| | _mMeasureObject->parseSelection(selection); |
| |
|
| | |
| | valueResult->setText(_mMeasureObject->getResultString()); |
| |
|
| |
|
| | |
| | initViewObject(_mMeasureObject); |
| | } |
| | } |
| |
|
| |
|
| | void TaskMeasure::initViewObject(Measure::MeasureBase* measure) |
| | { |
| | Gui::Document* guiDoc = Gui::Application::Instance->activeDocument(); |
| | if (!guiDoc) { |
| | return; |
| | } |
| |
|
| | Gui::ViewProvider* viewObject = guiDoc->getViewProvider(measure); |
| | if (!viewObject) { |
| | return; |
| | } |
| |
|
| | |
| | dynamic_cast<MeasureGui::ViewProviderMeasureBase*>(viewObject)->positionAnno(measure); |
| |
|
| | |
| | auto* prop = viewObject->getPropertyByName<App::PropertyBool>("ShowDelta"); |
| | setDeltaPossible(prop != nullptr); |
| | if (prop) { |
| | prop->setValue(showDelta->isChecked()); |
| | viewObject->update(prop); |
| | } |
| | } |
| |
|
| |
|
| | void TaskMeasure::closeDialog() |
| | { |
| | Gui::Control().closeDialog(); |
| | } |
| |
|
| |
|
| | void TaskMeasure::ensureGroup(Measure::MeasureBase* measurement) |
| | { |
| | |
| |
|
| | const char* measurementGroupName = "Measurements"; |
| | if (measurement == nullptr) { |
| | return; |
| | } |
| |
|
| | App::Document* doc = measurement->getDocument(); |
| | auto group = dynamic_cast<App::DocumentObjectGroup*>(doc->getObject(measurementGroupName)); |
| | if (!group || !group->isValid()) { |
| | group = doc->addObject<App::DocumentObjectGroup>( |
| | measurementGroupName, |
| | true, |
| | "MeasureGui::ViewProviderMeasureGroup" |
| | ); |
| | } |
| |
|
| | group->addObject(measurement); |
| | } |
| |
|
| |
|
| | |
| | void TaskMeasure::invoke() |
| | { |
| | update(); |
| | } |
| |
|
| | bool TaskMeasure::apply() |
| | { |
| | return apply(true); |
| | } |
| |
|
| | bool TaskMeasure::apply(bool reset) |
| | { |
| | ensureGroup(_mMeasureObject); |
| | _mMeasureObject = nullptr; |
| | if (reset) { |
| | this->reset(); |
| | } |
| |
|
| | |
| | App::GetApplication().closeActiveTransaction(); |
| | App::GetApplication().setActiveTransaction("Add Measurement"); |
| | return false; |
| | } |
| |
|
| | bool TaskMeasure::reject() |
| | { |
| | removeObject(); |
| | closeDialog(); |
| |
|
| | |
| | App::GetApplication().closeActiveTransaction(true); |
| | return false; |
| | } |
| |
|
| | void TaskMeasure::reset() |
| | { |
| | |
| | this->clearSelection(); |
| |
|
| | |
| | |
| | |
| |
|
| | this->update(); |
| | } |
| |
|
| |
|
| | void TaskMeasure::removeObject() |
| | { |
| | if (_mMeasureObject == nullptr) { |
| | return; |
| | } |
| | if (_mMeasureObject->isRemoving()) { |
| | return; |
| | } |
| |
|
| | _mMeasureObject->getDocument()->removeObject(_mMeasureObject->getNameInDocument()); |
| | _mMeasureObject = nullptr; |
| | } |
| |
|
| | bool TaskMeasure::hasSelection() |
| | { |
| | return !Gui::Selection().getSelection().empty(); |
| | } |
| |
|
| | void TaskMeasure::clearSelection() |
| | { |
| | Gui::Selection().clearSelection(); |
| | } |
| |
|
| | void TaskMeasure::onSelectionChanged(const Gui::SelectionChanges& msg) |
| | { |
| | |
| | if (msg.Type != Gui::SelectionChanges::AddSelection |
| | && msg.Type != Gui::SelectionChanges::RmvSelection |
| | && msg.Type != Gui::SelectionChanges::SetSelection |
| | && msg.Type != Gui::SelectionChanges::ClrSelection) { |
| |
|
| | return; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | const auto modifier = QGuiApplication::keyboardModifiers(); |
| | const bool ctrl = (modifier & Qt::ControlModifier) > 0; |
| | const bool shift = (modifier & Qt::ShiftModifier) > 0; |
| | |
| | const auto autosave = (mAutoSave && !shift) || (!mAutoSave && shift); |
| | if ((!ctrl && Gui::Selection().getSelectionStyle() == SelectionStyle::NormalSelection) |
| | || (ctrl && Gui::Selection().getSelectionStyle() == SelectionStyle::GreedySelection)) { |
| | if (autosave && buttonBox && buttonBox->button(QDialogButtonBox::Apply)->isEnabled()) { |
| | apply(false); |
| | } |
| | } |
| | update(); |
| | } |
| |
|
| | void TaskMeasure::setupShortcuts(QWidget* parent) |
| | { |
| | auto shortcutSave = new QShortcut(parent); |
| | shortcutSave->setKey(QKeySequence(QStringLiteral("Return"))); |
| | shortcutSave->setContext(Qt::ApplicationShortcut); |
| | connect(shortcutSave, &QShortcut::activated, this, &TaskMeasure::saveMeasurement); |
| |
|
| | auto shortcutQuit = new QShortcut(parent); |
| | shortcutQuit->setKey(QKeySequence(QStringLiteral("ESC"))); |
| | shortcutQuit->setContext(Qt::ApplicationShortcut); |
| | connect(shortcutQuit, &QShortcut::activated, this, &TaskMeasure::quitMeasurement); |
| | } |
| |
|
| | void TaskMeasure::saveMeasurement() |
| | { |
| | |
| | |
| | if (buttonBox) { |
| | buttonBox->button(QDialogButtonBox::Apply)->click(); |
| | } |
| | } |
| |
|
| | void TaskMeasure::quitMeasurement() |
| | { |
| | if (this->hasSelection()) { |
| | this->reset(); |
| | } |
| | else { |
| | this->reject(); |
| | } |
| | } |
| |
|
| | void TaskMeasure::onObjectDeleted(const App::DocumentObject& obj) |
| | { |
| | if (&obj == _mMeasureObject) { |
| | _mMeasureObject = nullptr; |
| | } |
| | } |
| |
|
| | void TaskMeasure::setDeltaPossible(bool possible) |
| | { |
| | showDelta->setVisible(possible); |
| | showDeltaLabel->setVisible(possible); |
| | } |
| |
|
| | void TaskMeasure::onModeChanged(int index) |
| | { |
| | explicitMode = (index != 0); |
| |
|
| | this->update(); |
| | } |
| |
|
| | void TaskMeasure::showDeltaChanged(int checkState) |
| | { |
| | delta = checkState == Qt::CheckState::Checked; |
| |
|
| | QSettings settings; |
| | settings.beginGroup(QLatin1String(taskMeasureSettingsGroup)); |
| | settings.setValue(QLatin1String(taskMeasureShowDeltaSettingsName), delta); |
| | settings.endGroup(); |
| | settings.sync(); |
| |
|
| | this->update(); |
| | } |
| |
|
| | void TaskMeasure::autoSaveChanged(bool checked) |
| | { |
| | mAutoSave = checked; |
| |
|
| | QSettings settings; |
| | settings.beginGroup(QLatin1String(taskMeasureSettingsGroup)); |
| | settings.setValue(QLatin1String(taskMeasureAutoSaveSettingsName), mAutoSave); |
| | settings.endGroup(); |
| | } |
| |
|
| | void TaskMeasure::newMeasurementBehaviourChanged(bool checked) |
| | { |
| | QSettings settings; |
| | settings.beginGroup(QLatin1String(taskMeasureSettingsGroup)); |
| | if (!checked) { |
| | Gui::Selection().setSelectionStyle(SelectionStyle::NormalSelection); |
| | settings.setValue(QLatin1String(taskMeasureGreedySelection), false); |
| | } |
| | else { |
| | Gui::Selection().setSelectionStyle(SelectionStyle::GreedySelection); |
| | settings.setValue(QLatin1String(taskMeasureGreedySelection), true); |
| | } |
| | settings.endGroup(); |
| | } |
| |
|
| | void TaskMeasure::setModeSilent(App::MeasureType* mode) |
| | { |
| | modeSwitch->blockSignals(true); |
| |
|
| | if (mode == nullptr) { |
| | modeSwitch->setCurrentIndex(0); |
| | } |
| | else { |
| | modeSwitch->setCurrentText(QString::fromLatin1(mode->label.c_str())); |
| | } |
| | modeSwitch->blockSignals(false); |
| | } |
| |
|
| | |
| | App::MeasureType* TaskMeasure::getMeasureType() |
| | { |
| | for (App::MeasureType* mType : App::MeasureManager::getMeasureTypes()) { |
| | if (mType->label.c_str() == modeSwitch->currentText().toLatin1()) { |
| | return mType; |
| | } |
| | } |
| | return nullptr; |
| | } |
| |
|