// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2009 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "TaskView.h" #include "TaskDialog.h" #include "TaskEditControl.h" #include #include #include #include using namespace Gui::TaskView; namespace sp = std::placeholders; //************************************************************************** //************************************************************************** // TaskWidget //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskWidget::TaskWidget(QWidget* parent) : QWidget(parent) {} TaskWidget::~TaskWidget() = default; //************************************************************************** //************************************************************************** // TaskGroup //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskGroup::TaskGroup(QWidget* parent) : QSint::ActionBox(parent) {} TaskGroup::TaskGroup(const QString& headerText, QWidget* parent) : QSint::ActionBox(headerText, parent) {} TaskGroup::TaskGroup(const QPixmap& icon, const QString& headerText, QWidget* parent) : QSint::ActionBox(icon, headerText, parent) {} TaskGroup::~TaskGroup() = default; void TaskGroup::actionEvent(QActionEvent* e) { QAction* action = e->action(); switch (e->type()) { case QEvent::ActionAdded: { this->createItem(action); break; } case QEvent::ActionChanged: { break; } case QEvent::ActionRemoved: { // cannot change anything break; } default: break; } } //************************************************************************** //************************************************************************** // TaskBox //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskBox::TaskBox(QWidget* parent) : QSint::ActionGroup(parent) , wasShown(false) { // override vertical size policy because otherwise task dialogs // whose needsFullSpace() returns true won't take full space. myGroup->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); } TaskBox::TaskBox(const QString& title, bool expandable, QWidget* parent) : QSint::ActionGroup(title, expandable, parent) , wasShown(false) { // override vertical size policy because otherwise task dialogs // whose needsFullSpace() returns true won't take full space. myGroup->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); } TaskBox::TaskBox(const QPixmap& icon, const QString& title, bool expandable, QWidget* parent) : QSint::ActionGroup(icon, title, expandable, parent) , wasShown(false) { // override vertical size policy because otherwise task dialogs // whose needsFullSpace() returns true won't take full space. myGroup->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); } QSize TaskBox::minimumSizeHint() const { // ActionGroup returns a size of 200x100 which leads to problems // when there are several task groups in a panel and the first // one is collapsed. In this case the task panel doesn't expand to // the actually required size and all the remaining groups are // squeezed into the available space and thus the widgets in there // often can't be used any more. // To fix this problem minimumSizeHint() is implemented to again // respect the layout's minimum size. QSize s1 = QSint::ActionGroup::minimumSizeHint(); QSize s2 = QWidget::minimumSizeHint(); return {qMax(s1.width(), s2.width()), qMax(s1.height(), s2.height())}; } TaskBox::~TaskBox() = default; void TaskBox::showEvent(QShowEvent*) { wasShown = true; } void TaskBox::hideGroupBox() { if (!wasShown) { // get approximate height int h = 0; int ct = groupLayout()->count(); for (int i = 0; i < ct; i++) { QLayoutItem* item = groupLayout()->itemAt(i); if (item && item->widget()) { QWidget* w = item->widget(); h += w->height(); } } m_tempHeight = m_fullHeight = h; // For the very first time the group gets shown // we cannot do the animation because the layouting // is not yet fully done m_foldDelta = 0; } else { m_tempHeight = m_fullHeight = myGroup->height(); m_foldDelta = m_fullHeight / myScheme->groupFoldSteps; } m_foldStep = 0.0; m_foldDirection = -1; // make sure to have the correct icon bool block = myHeader->blockSignals(true); myHeader->fold(); myHeader->blockSignals(block); myDummy->setFixedHeight(0); myDummy->hide(); myGroup->hide(); m_foldPixmap = QPixmap(); setFixedHeight(myHeader->height()); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); } bool TaskBox::isGroupVisible() const { return myGroup->isVisible(); } void TaskBox::actionEvent(QActionEvent* e) { QAction* action = e->action(); switch (e->type()) { case QEvent::ActionAdded: { auto label = new QSint::ActionLabel(action, this); this->addActionLabel(label, true, false); break; } case QEvent::ActionChanged: { break; } case QEvent::ActionRemoved: { // cannot change anything break; } default: break; } } //************************************************************************** //************************************************************************** // TaskPanel //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskPanel::TaskPanel(QWidget* parent) : QWidget(parent) { mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); this->setLayout(mainLayout); scrollArea = new QScrollArea(this); contextualPanelsLayout = new QVBoxLayout(); contextualPanelsLayout->setContentsMargins(0, 0, 0, 0); mainLayout->addLayout(contextualPanelsLayout); dialogLayout = new QVBoxLayout(); dialogLayout->setContentsMargins(0, 0, 0, 0); dialogLayout->setSpacing(0); mainLayout->addLayout(dialogLayout, 1); actionPanel = new QSint::ActionPanel(scrollArea); QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); sizePolicy.setHeightForWidth(actionPanel->sizePolicy().hasHeightForWidth()); actionPanel->setSizePolicy(sizePolicy); actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme()); scrollArea->setWidget(actionPanel); scrollArea->setWidgetResizable(true); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setMinimumWidth(200); dialogLayout->addWidget(scrollArea, 1); } TaskPanel::~TaskPanel() { for (QWidget* panel : contextualPanels) { delete panel; } } QSize TaskPanel::minimumSizeHint() const { // ActionPanel returns a size of 200x150 which leads to problems // when there are several task groups in the panel and the first // one is collapsed. In this case the task panel doesn't expand to // the actually required size and all the remaining groups are // squeezed into the available space and thus the widgets in there // often can't be used any more. // To fix this problem minimumSizeHint() is implemented to again // respect the layout's minimum size. QSize s1 = actionPanel->minimumSizeHint(); QSize s2 = QWidget::minimumSizeHint(); return {qMax(s1.width(), s2.width()), qMax(s1.height(), s2.height())}; } //************************************************************************** //************************************************************************** // TaskView //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskView::TaskView(QWidget* parent) : QStackedWidget(parent) , hGrp(Gui::WindowParameter::getDefaultParameter()->GetGroup("General")) { TaskWatcherPanel = new TaskPanel(this); addWidget(TaskWatcherPanel); Gui::Selection().Attach(this); // NOLINTBEGIN connectApplicationActiveDocument = App::GetApplication().signalActiveDocument.connect( std::bind(&Gui::TaskView::TaskView::slotActiveDocument, this, sp::_1) ); connectApplicationDeleteDocument = App::GetApplication().signalDeleteDocument.connect( std::bind(&Gui::TaskView::TaskView::slotDeletedDocument, this, sp::_1) ); connectApplicationClosedView = Gui::Application::Instance->signalCloseView.connect( std::bind(&Gui::TaskView::TaskView::slotViewClosed, this, sp::_1) ); connectApplicationUndoDocument = App::GetApplication().signalUndoDocument.connect( std::bind(&Gui::TaskView::TaskView::slotUndoDocument, this, sp::_1) ); connectApplicationRedoDocument = App::GetApplication().signalRedoDocument.connect( std::bind(&Gui::TaskView::TaskView::slotRedoDocument, this, sp::_1) ); connectApplicationInEdit = Gui::Application::Instance->signalInEdit.connect( std::bind(&Gui::TaskView::TaskView::slotInEdit, this, sp::_1) ); // NOLINTEND setShowTaskWatcher(hGrp->GetBool("ShowTaskWatcher", true)); connectShowTaskWatcherSetting = hGrp->Manager()->signalParamChanged.connect( [this](ParameterGrp* Param, ParameterGrp::ParamType Type, const char* name, const char* value) { if (Param == hGrp && Type == ParameterGrp::ParamType::FCBool && name && strcmp(name, "ShowTaskWatcher") == 0) { setShowTaskWatcher(value && *value == '1'); } } ); updateWatcher(); } TaskView::~TaskView() { connectApplicationActiveDocument.disconnect(); connectApplicationDeleteDocument.disconnect(); connectApplicationClosedView.disconnect(); connectApplicationUndoDocument.disconnect(); connectApplicationRedoDocument.disconnect(); connectApplicationInEdit.disconnect(); connectShowTaskWatcherSetting.disconnect(); Gui::Selection().Detach(this); // if well behaved, we should not have nay taskInfo at this point for (auto& taskInfo : taskInfos) { delete taskInfo.ActiveCtrl; delete taskInfo.ActiveDialog; delete taskInfo.taskPanel; } } bool TaskView::isEmpty(bool includeWatcher) const { std::optional active = currentTaskInfo(); if (active) { return false; } // There is no active task in the document if (includeWatcher) { for (auto* watcher : ActiveWatcher) { if (watcher->shouldShow()) { return false; } } } return true; } bool TaskView::event(QEvent* event) { // Workaround for a limitation in Qt (#0003794) // Line edits and spin boxes don't handle the key combination // Shift+Keypad button (if NumLock is activated) if (event->type() == QEvent::ShortcutOverride) { QWidget* focusWidget = qApp->focusWidget(); bool isLineEdit = qobject_cast(focusWidget); bool isSpinBox = qobject_cast(focusWidget); if (isLineEdit || isSpinBox) { auto kevent = static_cast(event); Qt::KeyboardModifiers ShiftKeypadModifier = Qt::ShiftModifier | Qt::KeypadModifier; if (kevent->modifiers() == Qt::NoModifier || kevent->modifiers() == Qt::ShiftModifier || kevent->modifiers() == Qt::KeypadModifier || kevent->modifiers() == ShiftKeypadModifier) { switch (kevent->key()) { case Qt::Key_Delete: case Qt::Key_Home: case Qt::Key_End: case Qt::Key_Backspace: case Qt::Key_Left: case Qt::Key_Right: kevent->accept(); default: break; } } } } return QWidget::event(event); } void TaskView::keyPressEvent(QKeyEvent* ke) { std::optional active = currentTaskInfo(); if (active) { if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) { // get all buttons of the complete task dialog QList list = this->findChildren(); for (auto pb : list) { if (pb->isDefault() && pb->isVisible()) { if (pb->isEnabled()) { #if defined(FC_OS_MACOSX) // #0001354: Crash on using Enter-Key for confirmation of chamfer or fillet // entries QPoint pos = QCursor::pos(); QCursor::setPos(pb->parentWidget()->mapToGlobal(pb->pos())); #endif pb->click(); #if defined(FC_OS_MACOSX) QCursor::setPos(pos); #endif } return; } } } else if (ke->key() == Qt::Key_Escape && active->ActiveDialog->isEscapeButtonEnabled()) { // get only the buttons of the button box QDialogButtonBox* box = active->ActiveCtrl->standardButtons(); QList list = box->buttons(); for (auto pb : list) { if (box->buttonRole(pb) == QDialogButtonBox::RejectRole) { if (pb->isEnabled()) { #if defined(FC_OS_MACOSX) // #0001354: Crash on using Enter-Key for confirmation of chamfer or fillet // entries QPoint pos = QCursor::pos(); QCursor::setPos(pb->parentWidget()->mapToGlobal(pb->pos())); #endif pb->click(); #if defined(FC_OS_MACOSX) QCursor::setPos(pos); #endif } return; } } // In case a task panel has no Close or Cancel button // then invoke resetEdit() directly // See also ViewProvider::eventCallback auto func = new Gui::TimerFunction(); func->setAutoDelete(true); Gui::Document* doc = Gui::Application::Instance->getDocument( active->ActiveDialog->getDocumentName().c_str() ); if (doc) { func->setFunction([doc]() { doc->resetEdit(); }); func->singleShot(0); } } } else { QWidget::keyPressEvent(ke); } } void TaskView::triggerMinimumSizeHint() { // NOLINTNEXTLINE QTimer::singleShot(100, this, &TaskView::adjustMinimumSizeHint); } void TaskView::adjustMinimumSizeHint() { QSize ms = minimumSizeHint(); setMinimumWidth(ms.width()); } QSize TaskView::minimumSizeHint() const { QSize ms = currentWidget()->minimumSizeHint(); int spacing = 0; if (QLayout* layout = currentWidget()->layout()) { spacing = 2 * layout->spacing(); } ms.setWidth(ms.width() + spacing); return ms; } void TaskView::slotActiveDocument(const App::Document& doc) { auto foundTaskInfo = std::ranges::find(taskInfos, &doc, &TaskInfo::Document); if (foundTaskInfo != taskInfos.end()) { setShownTaskInfo((foundTaskInfo - taskInfos.begin())); } else { setShownTaskInfo(-1); } if (foundTaskInfo == taskInfos.end()) { // at this point, active object of the active view returns None. // which is a problem if shouldShow of a watcher rely on the presence // of an active object (example Assembly). QTimer::singleShot(100, this, &TaskView::updateWatcher); } } void TaskView::slotInEdit(const Gui::ViewProviderDocumentObject& vp) { App::Document* doc = vp.getDocument()->getDocument(); if (std::ranges::find(taskInfos, doc, &TaskInfo::Document) == taskInfos.end()) { updateWatcher(); } } void TaskView::slotDeletedDocument(const App::Document& doc) { auto foundTaskInfo = std::ranges::find(taskInfos, &doc, &TaskInfo::Document); bool hasDialog = foundTaskInfo != taskInfos.end(); if (hasDialog && foundTaskInfo->ActiveDialog->isAutoCloseOnDeletedDocument()) { foundTaskInfo->ActiveDialog->autoClosedOnDeletedDocument(); removeDialog(foundTaskInfo); hasDialog = false; } if (!hasDialog) { updateWatcher(); } } void TaskView::slotViewClosed(const Gui::MDIView* view) { auto foundTaskInfo = std::ranges::find_if(taskInfos, [view](const TaskInfo& info) { return info.ActiveDialog->getAssociatedView() == view; }); bool hasDialog = foundTaskInfo != taskInfos.end(); // It can happen that only a view is closed an not the document if (hasDialog && foundTaskInfo->ActiveDialog->isAutoCloseOnClosedView()) { foundTaskInfo->ActiveDialog->autoClosedOnClosedView(); removeDialog(foundTaskInfo); hasDialog = false; } if (!hasDialog) { updateWatcher(); } } void TaskView::transactionChangeOnDocument(const App::Document& doc, bool undo) { auto foundTaskInfo = std::ranges::find(taskInfos, &doc, &TaskInfo::Document); bool hasDialog = foundTaskInfo != taskInfos.end(); if (hasDialog) { if (undo) { foundTaskInfo->ActiveDialog->onUndo(); } else { foundTaskInfo->ActiveDialog->onRedo(); } if (foundTaskInfo->ActiveDialog->isAutoCloseOnTransactionChange()) { foundTaskInfo->ActiveDialog->autoClosedOnTransactionChange(); removeDialog(foundTaskInfo); hasDialog = false; } } if (!hasDialog) { updateWatcher(); } } void TaskView::slotUndoDocument(const App::Document& doc) { transactionChangeOnDocument(doc, true); } void TaskView::slotRedoDocument(const App::Document& doc) { transactionChangeOnDocument(doc, false); } /// @cond DOXERR void TaskView::OnChange( Gui::SelectionSingleton::SubjectType& rCaller, Gui::SelectionSingleton::MessageType Reason ) { Q_UNUSED(rCaller); std::string temp; if (Reason.Type == SelectionChanges::AddSelection || Reason.Type == SelectionChanges::ClrSelection || Reason.Type == SelectionChanges::SetSelection || Reason.Type == SelectionChanges::RmvSelection) { if (!currentTaskInfo()) { updateWatcher(); } } } /// @endcond bool TaskView::showDialog(TaskDialog* dlg, App::Document* doc) { auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document); // if trying to open the same dialog twice nothing needs to be done if (foundTaskInfo != taskInfos.end() && foundTaskInfo->ActiveDialog == dlg) { return false; } assert(foundTaskInfo == taskInfos.end()); TaskInfo outInfo {.Document = doc}; // first create the control element, set it up and wire it: outInfo.ActiveCtrl = new TaskEditControl(this); outInfo.ActiveCtrl->buttonBox->setStandardButtons(dlg->getStandardButtons()); TaskDialogAttorney::setButtonBox(dlg, outInfo.ActiveCtrl->buttonBox); const std::vector& cont = dlg->getDialogContent(); // give to task dialog to customize the button box dlg->modifyStandardButtons(outInfo.ActiveCtrl->buttonBox); outInfo.taskPanel = new TaskPanel(this); if (dlg->buttonPosition() == TaskDialog::North) { // Add button box to the top of the main layout outInfo.taskPanel->dialogLayout->insertWidget(0, outInfo.ActiveCtrl); for (const auto& it : cont) { outInfo.taskPanel->actionPanel->addWidget(it); } } else { for (const auto& it : cont) { outInfo.taskPanel->actionPanel->addWidget(it); } // Add button box to the bottom of the main layout outInfo.taskPanel->dialogLayout->addWidget(outInfo.ActiveCtrl); } outInfo.taskPanel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme()); if (!dlg->needsFullSpace()) { outInfo.taskPanel->actionPanel->addStretch(); } // set as active Dialog outInfo.ActiveDialog = dlg; outInfo.ActiveDialog->open(); // clang-format off // make connection to the needed signals connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::accepted, this, [doc, this]{ accept(doc); }); connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::rejected, this, [doc, this]{ reject(doc); }); connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::helpRequested, this, [doc, this]{ helpRequested(doc); }); connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::clicked, this, [doc, this](QAbstractButton *button) { clicked(button, doc); }); // clang-format on // This will hide whatever was shown in the taskview taskInfos.push_back(outInfo); addWidget(outInfo.taskPanel); setShownTaskInfo(taskInfos.size() - 1); saveCurrentWidth(); getMainWindow()->updateActions(); triggerMinimumSizeHint(); Q_EMIT taskUpdate(); OverlayManager::instance()->refresh(); return true; } void TaskView::removeDialog(App::Document* doc) { auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document); if (foundTaskInfo != taskInfos.end()) { removeDialog(foundTaskInfo); } } void TaskView::removeDialog(std::vector::iterator infoIt) { if (infoIt == taskInfos.end()) { return; } getMainWindow()->updateActions(); std::optional remove = std::nullopt; if (infoIt->ActiveDialog) { // See 'accept' and 'reject' if (infoIt->ActiveDialog->property("taskview_accept_or_reject").isNull()) { const std::vector& cont = infoIt->ActiveDialog->getDialogContent(); for (const auto& it : cont) { infoIt->taskPanel->actionPanel->removeWidget(it); } remove = *infoIt; taskInfos.erase(infoIt); removeWidget(remove->taskPanel); } else { infoIt->ActiveDialog->setProperty("taskview_remove_dialog", true); } } // put the watcher back in control removeTaskWatcher(); addTaskWatcher(); if (remove) { remove->ActiveDialog->closed(); remove->ActiveDialog->emitDestructionSignal(); delete remove->ActiveCtrl; delete remove->ActiveDialog; delete remove->taskPanel; } tryRestoreWidth(); triggerMinimumSizeHint(); OverlayManager::instance()->refresh(); } void TaskView::setShowTaskWatcher(bool show) { if (showTaskWatcher == show) { return; } showTaskWatcher = show; if (show) { addTaskWatcher(); } else { clearTaskWatcher(); } } void TaskView::updateWatcher() { if (!showTaskWatcher) { return; } if (ActiveWatcher.empty()) { auto panel = Gui::Control().taskPanel(); if (panel && !panel->ActiveWatcher.empty()) { takeTaskWatcher(panel); } } // In case a child of the TaskView has the focus and get hidden we have // to make sure to set the focus on a widget that won't be hidden or // deleted because otherwise Qt may forward the focus via focusNextPrevChild() // to the mdi area which may switch to another mdi view which is not an // acceptable behaviour. QWidget* fw = QApplication::focusWidget(); if (!fw) { this->setFocus(); } QPointer fwp = fw; while (fw && !fw->isWindow()) { if (fw == this) { this->setFocus(); break; } fw = fw->parentWidget(); } // add all widgets for all watcher to the task view for (const auto& it : ActiveWatcher) { bool match = it->shouldShow(); std::vector& cont = it->getWatcherContent(); for (auto& it2 : cont) { if (match) { it2->show(); } else { it2->hide(); } } } // In case the previous widget that had the focus is still visible // give it the focus back. if (fwp && fwp->isVisible()) { fwp->setFocus(); } triggerMinimumSizeHint(); Q_EMIT taskUpdate(); } void TaskView::addTaskWatcher(const std::vector& Watcher) { // remove and delete the old set of TaskWatcher for (TaskWatcher* tw : ActiveWatcher) { delete tw; } ActiveWatcher = Watcher; addTaskWatcher(); } void TaskView::takeTaskWatcher(TaskView* other) { clearTaskWatcher(); ActiveWatcher.swap(other->ActiveWatcher); other->clearTaskWatcher(); if (isEmpty(false)) { addTaskWatcher(); } } void TaskView::clearTaskWatcher() { std::vector watcher; removeTaskWatcher(); // make sure to delete the old watchers addTaskWatcher(watcher); } void TaskView::addTaskWatcher() { if (!showTaskWatcher) { setShownTaskInfo(-1); // Switch to the empty taskwatcher panel return; } // add all widgets for all watcher to the task view for (TaskWatcher* tw : ActiveWatcher) { std::vector& cont = tw->getWatcherContent(); for (QWidget* w : cont) { TaskWatcherPanel->actionPanel->addWidget(w); } } if (!ActiveWatcher.empty()) { TaskWatcherPanel->actionPanel->addStretch(); } // Workaround to avoid a crash in Qt. See also // https://forum.freecad.org/viewtopic.php?f=8&t=39187 // // Notify the button box about a style change so that it can // safely delete the style animation of its push buttons. auto box = TaskWatcherPanel->mainLayout->findChild(); if (box) { QEvent event(QEvent::StyleChange); QApplication::sendEvent(box, &event); } TaskWatcherPanel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme()); setShownTaskInfo(-1); } void TaskView::saveCurrentWidth() { if (shouldRestoreWidth()) { if (auto parent = qobject_cast(parentWidget())) { currentWidth = parent->width(); } } } void TaskView::tryRestoreWidth() { if (shouldRestoreWidth()) { if (auto parent = qobject_cast(parentWidget())) { Gui::getMainWindow()->resizeDocks({parent}, {currentWidth}, Qt::Horizontal); } } } void TaskView::setRestoreWidth(bool on) { restoreWidth = on; } bool TaskView::shouldRestoreWidth() const { return restoreWidth; } std::optional TaskView::currentTaskInfo() const { // Index 0 is taskWatcher's panel if (currentIndex() <= 0) { return std::nullopt; } return taskInfos[currentIndex() - 1]; } TaskDialog* TaskView::dialog(App::Document* doc) { auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document); return foundTaskInfo == taskInfos.end() ? nullptr : foundTaskInfo->ActiveDialog; } void TaskView::setShownTaskInfo(int index) { int stackedIndex = 0; int initIndex = currentIndex(); if (index < 0 || index >= taskInfos.size()) { updateWatcher(); stackedIndex = 0; // Show task watcher } else { stackedIndex = index + 1; } if (stackedIndex == initIndex) { return; // Nothing to be done } if (initIndex > 0) { Gui::Selection().rmvSelectionGate(); taskInfos[initIndex - 1].ActiveDialog->deactivate(); } if (stackedIndex > 0) { taskInfos[stackedIndex - 1].ActiveDialog->activate(); } setCurrentIndex(stackedIndex); } void TaskView::removeTaskWatcher() { // In case a child of the TaskView has the focus and get hidden we have // to make sure that set the focus on a widget that won't be hidden or // deleted because otherwise Qt may forward the focus via focusNextPrevChild() // to the mdi area which may switch to another mdi view which is not an // acceptable behaviour. QWidget* fw = QApplication::focusWidget(); if (!fw) { this->setFocus(); } while (fw && !fw->isWindow()) { if (fw == this) { this->setFocus(); break; } fw = fw->parentWidget(); } // remove all widgets for (TaskWatcher* tw : ActiveWatcher) { std::vector& cont = tw->getWatcherContent(); for (QWidget* w : cont) { w->hide(); TaskWatcherPanel->actionPanel->removeWidget(w); } } TaskWatcherPanel->actionPanel->removeStretch(); } void TaskView::accept(App::Document* doc) { auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document); if (foundTaskInfo == taskInfos.end()) { // Protect against segfaults due to out-of-order deletions Base::Console().warning("ActiveDialog was null in call to TaskView::accept()\n"); return; } // Make sure that if 'accept' calls 'closeDialog' the deletion is postponed until // the dialog leaves the 'accept' method foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", true); bool success = foundTaskInfo->ActiveDialog->accept(); foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", QVariant()); if (success || foundTaskInfo->ActiveDialog->property("taskview_remove_dialog").isValid()) { removeDialog(doc); } } void TaskView::reject(App::Document* doc) { auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document); if (foundTaskInfo == taskInfos.end()) { // Protect against segfaults due to out-of-order deletions Base::Console().warning("ActiveDialog was null in call to TaskView::reject()\n"); return; } // Make sure that if 'reject' calls 'closeDialog' the deletion is postponed until // the dialog leaves the 'reject' method foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", true); bool success = foundTaskInfo->ActiveDialog->reject(); foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", QVariant()); if (success || foundTaskInfo->ActiveDialog->property("taskview_remove_dialog").isValid()) { removeDialog(doc); } } void TaskView::helpRequested(App::Document* doc) { auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document); if (foundTaskInfo != taskInfos.end()) { foundTaskInfo->ActiveDialog->helpRequested(); } } void TaskView::clicked(QAbstractButton* button, App::Document* doc) { auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document); if (foundTaskInfo != taskInfos.end()) { int id = foundTaskInfo->ActiveCtrl->buttonBox->standardButton(button); foundTaskInfo->ActiveDialog->clicked(id); } } void TaskView::clearActionStyle() { std::optional current = currentTaskInfo(); TaskPanel* panel = current ? current->taskPanel : TaskWatcherPanel; static_cast(QSint::ActionPanelScheme::defaultScheme())->clearActionStyle(); panel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme()); } void TaskView::restoreActionStyle() { std::optional current = currentTaskInfo(); TaskPanel* panel = current ? current->taskPanel : TaskWatcherPanel; static_cast(QSint::ActionPanelScheme::defaultScheme()) ->restoreActionStyle(); panel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme()); } void TaskView::addContextualPanel(QWidget* panel, App::Document* doc) { auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document); if (!panel || foundTaskInfo == taskInfos.end() || foundTaskInfo->taskPanel->contextualPanels.contains(panel)) { return; } foundTaskInfo->taskPanel->contextualPanelsLayout->addWidget(panel); foundTaskInfo->taskPanel->contextualPanels.append(panel); panel->show(); triggerMinimumSizeHint(); Q_EMIT taskUpdate(); } void TaskView::removeContextualPanel(QWidget* panel, App::Document* doc) { auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document); if (!panel || foundTaskInfo == taskInfos.end() || !foundTaskInfo->taskPanel->contextualPanels.contains(panel)) { return; } foundTaskInfo->taskPanel->contextualPanelsLayout->removeWidget(panel); foundTaskInfo->taskPanel->contextualPanels.removeOne(panel); panel->deleteLater(); triggerMinimumSizeHint(); Q_EMIT taskUpdate(); } #include "moc_TaskView.cpp"