ResInsight/Fwk/AppFwk/cafUserInterface/cafPdmUiTreeSelectionEditor.cpp
Gaute Lindkvist 7fd3dac2ef Revert "#3751 Fix keyboard selection in Summary Plot Editor"
This reverts commit ab0fa2fb19.

* This change should never have been merged back into the repo.
2018-11-30 09:45:50 +01:00

685 lines
24 KiB
C++

//##################################################################################################
//
// 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 <<http://www.gnu.org/licenses/gpl.html>>
// 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 <<http://www.gnu.org/licenses/lgpl-2.1.html>>
// for more details.
//
//##################################################################################################
#include "cafPdmUiTreeSelectionEditor.h"
#include "cafAssert.h"
#include "cafPdmObject.h"
#include "cafPdmUiCommandSystemProxy.h"
#include "cafPdmUiTreeSelectionQModel.h"
#include <QBoxLayout>
#include <QCheckBox>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QPainter>
#include <QPalette>
#include <QSortFilterProxyModel>
#include <QStyleOption>
#include <QTreeView>
#include <algorithm>
//==================================================================================================
/// 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<PdmOptionItemInfo> 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