//################################################################################################## // // Custom Visualization Core library // // This library may be used under the terms of either the GNU General Public License or // the GNU Lesser General Public License as follows: // // GNU General Public License Usage // This library is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 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 General Public License at <> // for more details. // // GNU Lesser General Public License Usage // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2.1 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 Lesser General Public License at <> // for more details. // //################################################################################################## #include "cafPdmUiTreeSelectionEditor.h" #include "cafAssert.h" #include "cafPdmObject.h" #include "cafPdmUiCommandSystemProxy.h" #include "cafPdmUiTreeSelectionQModel.h" #include #include #include #include #include #include #include #include #include #include #include #include //================================================================================================== /// Helper class used to control height of size hint //================================================================================================== class QTreeViewHeightHint : public QTreeView { public: explicit QTreeViewHeightHint(QWidget *parent = nullptr) : m_heightHint(-1) { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QSize sizeHint() const override { QSize mySize = QTreeView::sizeHint(); if (m_heightHint > 0) { mySize.setHeight(m_heightHint); } return mySize; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void setHeightHint(int heightHint) { m_heightHint = heightHint; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void keyPressEvent(QKeyEvent *event) { QTreeView::keyPressEvent(event); if (event->key() == Qt::Key_Down || event->key() == Qt::Key_Up || event->key() == Qt::Key_Home || event->key() == Qt::Key_End || event->key() == Qt::Key_PageDown || event->key() == Qt::Key_PageUp) { emit clicked(currentIndex()); } } private: int m_heightHint; }; //================================================================================================== /// //================================================================================================== class FilterLeafNodesOnlyProxyModel : public QSortFilterProxyModel { public: FilterLeafNodesOnlyProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) { } protected: //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override { QModelIndex index = sourceModel()->index(source_row, 0, source_parent); if (sourceModel()->hasChildren(index)) { // Always include node if node has children return true; } return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } }; //-------------------------------------------------------------------------------------------------- /// Ported parts of placeholder text painting from Qt 4.7 /// setPlaceholderText() was introduced in Qt 4.7, /// and this class is intended to be removed when Qt is upgraded //-------------------------------------------------------------------------------------------------- class PlaceholderLineEdit : public QLineEdit { public: explicit PlaceholderLineEdit(const QString& placeholderText, QWidget* parent = nullptr) : QLineEdit(parent), m_placeholderText(placeholderText) { } private: //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void paintEvent(QPaintEvent* paintEvent) override { QPainter p(this); QRect r = rect(); QPalette pal = palette(); QStyleOptionFrameV2 panel; initStyleOption(&panel); style()->drawPrimitive(QStyle::PE_PanelLineEdit, &panel, &p, this); r = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this); int left = 0; int top = 0; int right = 0; int bottom = 0; getTextMargins(&left, &top, &right, &bottom); r.setX(r.x() + left); r.setY(r.y() + top); r.setRight(r.right() - right); r.setBottom(r.bottom() - bottom); p.setClipRect(r); QFontMetrics fm = fontMetrics(); const int horizontalMargin = 2; const int vscroll = r.y() + (r.height() - fm.height() + 1) / 2; QRect lineRect(r.x() + horizontalMargin, vscroll, r.width() - 2*horizontalMargin, fm.height()); int minLB = qMax(0, -fm.minLeftBearing()); if (text().isEmpty()) { if (!hasFocus() && !m_placeholderText.isEmpty()) { QColor col = pal.text().color(); col.setAlpha(128); QPen oldpen = p.pen(); p.setPen(col); lineRect.adjust(minLB, 0, 0, 0); QString elidedText = fm.elidedText(m_placeholderText, Qt::ElideRight, lineRect.width()); p.drawText(lineRect, Qt::AlignLeft | Qt::TextWordWrap, elidedText); p.setPen(oldpen); return; } } return QLineEdit::paintEvent(paintEvent); } private: QString m_placeholderText; }; namespace caf { //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- CAF_PDM_UI_FIELD_EDITOR_SOURCE_INIT(PdmUiTreeSelectionEditor); //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- PdmUiTreeSelectionEditor::PdmUiTreeSelectionEditor() : m_model(nullptr), m_proxyModel(nullptr) { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- PdmUiTreeSelectionEditor::~PdmUiTreeSelectionEditor() { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::configureAndUpdateUi(const QString& uiConfigName) { // Label CAF_ASSERT(!m_label.isNull()); PdmUiFieldEditorHandle::updateLabelFromField(m_label, uiConfigName); if (!m_model) { m_model = new caf::PdmUiTreeSelectionQModel(this); m_proxyModel = new FilterLeafNodesOnlyProxyModel(this); m_proxyModel->setSourceModel(m_model); m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); m_treeView->setModel(m_proxyModel); } bool optionsOnly = true; QList options = uiField()->valueOptions(&optionsOnly); bool itemCountHasChaged = false; if (m_model->optionItemCount() != options.size()) itemCountHasChaged = true; QVariant fieldValue = uiField()->uiValue(); m_model->setUiValueCache(&fieldValue); // TODO: If the count is different between incoming and current list of items, // use cafQTreeViewStateSerializer to restore collapsed state m_model->setOptions(this, options); if (itemCountHasChaged) { m_treeView->expandAll(); } if (PdmUiTreeSelectionQModel::isSingleValueField(fieldValue)) { m_textFilterLineEdit->hide(); m_toggleAllCheckBox->hide(); } else if (PdmUiTreeSelectionQModel::isMultipleValueField(fieldValue)) { caf::PdmUiObjectHandle* uiObject = uiObj(uiField()->fieldHandle()->ownerObject()); if (uiObject) { uiObject->editorAttribute(uiField()->fieldHandle(), uiConfigName, &m_attributes); } if (m_attributes.singleSelectionMode) { m_treeView->setSelectionMode(QAbstractItemView::SingleSelection); m_treeView->setContextMenuPolicy(Qt::NoContextMenu); m_model->enableSingleSelectionMode(m_attributes.singleSelectionMode); } else { m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); } connect(m_treeView, SIGNAL(clicked(QModelIndex)), this, SLOT(slotClicked(QModelIndex)), Qt::UniqueConnection); if (!m_attributes.showTextFilter) { m_textFilterLineEdit->hide(); } if (m_attributes.singleSelectionMode || !m_attributes.showToggleAllCheckbox) { m_toggleAllCheckBox->hide(); } else { if (options.size() == 0) { m_toggleAllCheckBox->setChecked(false); } else { QModelIndexList indices = allVisibleSourceModelIndices(); if (indices.size() > 0) { size_t editableItems = 0u; size_t checkedEditableItems = 0u; for (auto mi : indices) { if (!m_model->isReadOnly(mi)) { editableItems++; if (m_model->isChecked(mi)) { checkedEditableItems++; } } } bool allItemsChecked = (editableItems > 0u && checkedEditableItems == editableItems); m_toggleAllCheckBox->setChecked(allItemsChecked); } } } } // If the tree doesn't have grand children we treat this as a straight list m_treeView->setRootIsDecorated(m_model->hasGrandChildren()); m_model->resetUiValueCache(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QWidget* PdmUiTreeSelectionEditor::createEditorWidget(QWidget* parent) { QFrame* frame = new QFrame(parent); QVBoxLayout* layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); frame->setLayout(layout); { QHBoxLayout* headerLayout = new QHBoxLayout; headerLayout->setContentsMargins(0, 0, 0, 0); layout->addLayout(headerLayout); PdmUiTreeSelectionEditorAttribute attrib; m_toggleAllCheckBox = new QCheckBox(); headerLayout->addWidget(m_toggleAllCheckBox); connect(m_toggleAllCheckBox, SIGNAL(clicked(bool)), this, SLOT(slotToggleAll())); m_textFilterLineEdit = new PlaceholderLineEdit("Click to add filter"); // TODO: setPlaceholderText() was introduced in Qt 4.7 // Use QLineEdit instead of PlaceholderLineEdit when Qt is upgraded // m_textFilterLineEdit->setPlaceholderText("Click to add filter"); headerLayout->addWidget(m_textFilterLineEdit); connect(m_textFilterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotTextFilterChanged())); } QTreeViewHeightHint* treeViewHeightHint = new QTreeViewHeightHint(parent); treeViewHeightHint->setHeightHint(2000); treeViewHeightHint->setHeaderHidden(true); m_treeView = treeViewHeightHint; m_treeView->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_treeView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customMenuRequested(QPoint))); layout->addWidget(treeViewHeightHint); return frame; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QWidget* PdmUiTreeSelectionEditor::createLabelWidget(QWidget * parent) { m_label = new QLabel(parent); return m_label; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QMargins PdmUiTreeSelectionEditor::calculateLabelContentMargins() const { QSize editorSize = m_textFilterLineEdit->sizeHint(); QSize labelSize = m_label->sizeHint(); int heightDiff = editorSize.height() - labelSize.height(); QMargins contentMargins = m_label->contentsMargins(); if (heightDiff > 0) { contentMargins.setTop(contentMargins.top() + heightDiff / 2); contentMargins.setBottom(contentMargins.bottom() + heightDiff / 2); } return contentMargins; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::customMenuRequested(const QPoint& pos) { QMenu menu; QModelIndexList selectedIndexes = m_treeView->selectionModel()->selectedIndexes(); bool onlyHeadersInSelection = true; for (auto mi : selectedIndexes) { QVariant v = m_proxyModel->data(mi, PdmUiTreeSelectionQModel::headingRole()); if (v.toBool() == false) { onlyHeadersInSelection = false; } } if (onlyHeadersInSelection && selectedIndexes.size() > 0) { { QAction* act = new QAction("Sub Items On", this); connect(act, SIGNAL(triggered()), SLOT(slotSetSubItemsOn())); menu.addAction(act); } { QAction* act = new QAction("Sub Items Off", this); connect(act, SIGNAL(triggered()), SLOT(slotSetSubItemsOff())); menu.addAction(act); } } else if (selectedIndexes.size() > 0) { { QAction* act = new QAction("Set Selected On", this); connect(act, SIGNAL(triggered()), SLOT(slotSetSelectedOn())); menu.addAction(act); } { QAction* act = new QAction("Set Selected Off", this); connect(act, SIGNAL(triggered()), SLOT(slotSetSelectedOff())); menu.addAction(act); } } if (menu.actions().size() > 0) { // Qt doc: QAbstractScrollArea and its subclasses that map the context menu event to coordinates of the viewport(). QPoint globalPos = m_treeView->viewport()->mapToGlobal(pos); menu.exec(globalPos); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::slotSetSelectedOn() { this->setCheckedStateOfSelected(true); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::slotSetSelectedOff() { this->setCheckedStateOfSelected(false); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::setCheckedStateOfSelected(bool checked) { if (!m_proxyModel) return; QItemSelection selectionInProxyModel = m_treeView->selectionModel()->selection(); QItemSelection selectionInSourceModel = m_proxyModel->mapSelectionToSource(selectionInProxyModel); m_model->setCheckedStateForItems(selectionInSourceModel.indexes(), checked); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::slotSetSubItemsOn() { this->setCheckedStateForSubItemsOfSelected(true); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::slotSetSubItemsOff() { this->setCheckedStateForSubItemsOfSelected(false); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::setCheckedStateForSubItemsOfSelected(bool checked) { QModelIndexList selectedProxyIndexes = m_treeView->selectionModel()->selectedIndexes(); QModelIndexList sourceIndexesToSubItems; for (auto mi : selectedProxyIndexes) { for (int i = 0; i < m_proxyModel->rowCount(mi); i++) { QModelIndex childProxyIndex = m_proxyModel->index(i, 0, mi); sourceIndexesToSubItems.push_back(m_proxyModel->mapToSource(childProxyIndex)); } } m_model->setCheckedStateForItems(sourceIndexesToSubItems, checked); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::slotToggleAll() { if (m_toggleAllCheckBox->isChecked()) { checkAllItems(); } else { unCheckAllItems(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::slotTextFilterChanged() { QString searchString = m_textFilterLineEdit->text(); searchString += "*"; // Escape the characters '[' and ']' as these have special meaning for a search string // To be able to search for vector names in brackets, these must be escaped // See "Wildcard Matching" in Qt documentation searchString.replace("[", "\\["); searchString.replace("]", "\\]"); QRegExp searcher(searchString, Qt::CaseInsensitive, QRegExp::WildcardUnix); m_proxyModel->setFilterRegExp(searcher); updateUi(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::slotClicked(const QModelIndex& index) { if (m_attributes.setCurrentIndexWhenItemIsChecked && index.isValid()) { QModelIndexList selectedIndexes = m_treeView->selectionModel()->selectedIndexes(); if (selectedIndexes.size() < 2) { QVariant v = m_proxyModel->data(index, Qt::CheckStateRole); if (v == Qt::Checked) { m_treeView->setCurrentIndex(index); } } } currentChanged(index); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::currentChanged(const QModelIndex& current) { if (m_attributes.singleSelectionMode) { m_proxyModel->setData(current, true, Qt::CheckStateRole); } if (m_attributes.fieldToReceiveCurrentItemValue) { PdmUiFieldHandle* uiFieldHandle = m_attributes.fieldToReceiveCurrentItemValue->uiCapability(); if (uiFieldHandle) { QVariant v = m_proxyModel->data(current, PdmUiTreeSelectionQModel::optionItemValueRole()); PdmUiCommandSystemProxy::instance()->setUiValueToField(uiFieldHandle, v); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::checkAllItems() { QModelIndexList indices = allVisibleSourceModelIndices(); m_model->setCheckedStateForItems(indices, true); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::unCheckAllItems() { QModelIndexList indices = allVisibleSourceModelIndices(); m_model->setCheckedStateForItems(indices, false); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QModelIndexList PdmUiTreeSelectionEditor::allVisibleSourceModelIndices() const { QModelIndexList indices; recursiveAppendVisibleSourceModelIndices(QModelIndex(), &indices); return indices; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void PdmUiTreeSelectionEditor::recursiveAppendVisibleSourceModelIndices(const QModelIndex& parent, QModelIndexList* sourceModelIndices) const { for (int row = 0; row < m_proxyModel->rowCount(parent); row++) { QModelIndex mi = m_proxyModel->index(row, 0, parent); if (mi.isValid()) { QVariant v = m_proxyModel->data(mi, PdmUiTreeSelectionQModel::headingRole()); if (v.toBool() == false) { sourceModelIndices->push_back(m_proxyModel->mapToSource(mi)); } recursiveAppendVisibleSourceModelIndices(mi, sourceModelIndices); } } } } // end namespace caf