| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 | /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying   file Copyright.txt or https://cmake.org/licensing for details.  */#include "QCMakeCacheView.h"#include "QCMakeWidgets.h"#include <QApplication>#include <QEvent>#include <QHBoxLayout>#include <QHeaderView>#include <QKeyEvent>#include <QMetaProperty>#include <QSortFilterProxyModel>#include <QStyle>// filter for searchesclass QCMakeSearchFilter : public QSortFilterProxyModel{public:  QCMakeSearchFilter(QObject* o)    : QSortFilterProxyModel(o)  {  }protected:  bool filterAcceptsRow(int row, const QModelIndex& p) const override  {    QStringList strs;    const QAbstractItemModel* m = this->sourceModel();    QModelIndex idx = m->index(row, 0, p);    // if there are no children, get strings for column 0 and 1    if (!m->hasChildren(idx)) {      strs.append(m->data(idx).toString());      idx = m->index(row, 1, p);      strs.append(m->data(idx).toString());    } else {      // get strings for children entries to compare with      // instead of comparing with the parent      int num = m->rowCount(idx);      for (int i = 0; i < num; i++) {        QModelIndex tmpidx = m->index(i, 0, idx);        strs.append(m->data(tmpidx).toString());        tmpidx = m->index(i, 1, idx);        strs.append(m->data(tmpidx).toString());      }    }    // check all strings for a match    foreach (QString const& str, strs) {      if (str.contains(this->filterRegExp())) {        return true;      }    }    return false;  }};// filter for searchesclass QCMakeAdvancedFilter : public QSortFilterProxyModel{public:  QCMakeAdvancedFilter(QObject* o)    : QSortFilterProxyModel(o)    , ShowAdvanced(false)  {  }  void setShowAdvanced(bool f)  {    this->ShowAdvanced = f;    this->invalidate();  }  bool showAdvanced() const { return this->ShowAdvanced; }protected:  bool ShowAdvanced;  bool filterAcceptsRow(int row, const QModelIndex& p) const override  {    const QAbstractItemModel* m = this->sourceModel();    QModelIndex idx = m->index(row, 0, p);    // if there are no children    if (!m->hasChildren(idx)) {      bool adv = m->data(idx, QCMakeCacheModel::AdvancedRole).toBool();      return !adv || this->ShowAdvanced;    }    // check children    int num = m->rowCount(idx);    for (int i = 0; i < num; i++) {      bool accept = this->filterAcceptsRow(i, idx);      if (accept) {        return true;      }    }    return false;  }};QCMakeCacheView::QCMakeCacheView(QWidget* p)  : QTreeView(p){  // hook up our model and search/filter proxies  this->CacheModel = new QCMakeCacheModel(this);  this->AdvancedFilter = new QCMakeAdvancedFilter(this);  this->AdvancedFilter->setSourceModel(this->CacheModel);  this->AdvancedFilter->setDynamicSortFilter(true);  this->SearchFilter = new QCMakeSearchFilter(this);  this->SearchFilter->setSourceModel(this->AdvancedFilter);  this->SearchFilter->setFilterCaseSensitivity(Qt::CaseInsensitive);  this->SearchFilter->setDynamicSortFilter(true);  this->setModel(this->SearchFilter);  // our delegate for creating our editors  QCMakeCacheModelDelegate* delegate = new QCMakeCacheModelDelegate(this);  this->setItemDelegate(delegate);  this->setUniformRowHeights(true);  this->setEditTriggers(QAbstractItemView::AllEditTriggers);  // tab, backtab doesn't step through items  this->setTabKeyNavigation(false);  this->setRootIsDecorated(false);}bool QCMakeCacheView::event(QEvent* e){  if (e->type() == QEvent::Show) {    this->header()->setDefaultSectionSize(this->viewport()->width() / 2);  }  return QTreeView::event(e);}QCMakeCacheModel* QCMakeCacheView::cacheModel() const{  return this->CacheModel;}QModelIndex QCMakeCacheView::moveCursor(CursorAction act,                                        Qt::KeyboardModifiers mod){  // want home/end to go to begin/end of rows, not columns  if (act == MoveHome) {    return this->model()->index(0, 1);  }  if (act == MoveEnd) {    return this->model()->index(this->model()->rowCount() - 1, 1);  }  return QTreeView::moveCursor(act, mod);}void QCMakeCacheView::setShowAdvanced(bool s){  this->SearchFilter->invalidate();  this->AdvancedFilter->setShowAdvanced(s);}bool QCMakeCacheView::showAdvanced() const{  return this->AdvancedFilter->showAdvanced();}void QCMakeCacheView::setSearchFilter(const QString& s){  this->SearchFilter->setFilterFixedString(s);}QCMakeCacheModel::QCMakeCacheModel(QObject* p)  : QStandardItemModel(p)  , EditEnabled(true)  , NewPropertyCount(0)  , View(FlatView){  this->ShowNewProperties = true;  QStringList labels;  labels << tr("Name") << tr("Value");  this->setHorizontalHeaderLabels(labels);}QCMakeCacheModel::~QCMakeCacheModel() = default;static uint qHash(const QCMakeProperty& p){  return qHash(p.Key);}void QCMakeCacheModel::setShowNewProperties(bool f){  this->ShowNewProperties = f;}void QCMakeCacheModel::clear(){  this->QStandardItemModel::clear();  this->NewPropertyCount = 0;  QStringList labels;  labels << tr("Name") << tr("Value");  this->setHorizontalHeaderLabels(labels);}void QCMakeCacheModel::setProperties(const QCMakePropertyList& props){  this->beginResetModel();  QSet<QCMakeProperty> newProps;  QSet<QCMakeProperty> newProps2;  if (this->ShowNewProperties) {    newProps = props.toSet();    newProps2 = newProps;    QSet<QCMakeProperty> oldProps = this->properties().toSet();    oldProps.intersect(newProps);    newProps.subtract(oldProps);    newProps2.subtract(newProps);  } else {    newProps2 = props.toSet();  }  bool b = this->blockSignals(true);  this->clear();  this->NewPropertyCount = newProps.size();  if (View == FlatView) {    QCMakePropertyList newP = newProps.toList();    QCMakePropertyList newP2 = newProps2.toList();#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)    std::sort(newP.begin(), newP.end());    std::sort(newP2.begin(), newP2.end());#else    qSort(newP);    qSort(newP2);#endif    int row_count = 0;    foreach (QCMakeProperty const& p, newP) {      this->insertRow(row_count);      this->setPropertyData(this->index(row_count, 0), p, true);      row_count++;    }    foreach (QCMakeProperty const& p, newP2) {      this->insertRow(row_count);      this->setPropertyData(this->index(row_count, 0), p, false);      row_count++;    }  } else if (this->View == GroupView) {    QMap<QString, QCMakePropertyList> newPropsTree;    QCMakeCacheModel::breakProperties(newProps, newPropsTree);    QMap<QString, QCMakePropertyList> newPropsTree2;    QCMakeCacheModel::breakProperties(newProps2, newPropsTree2);    QStandardItem* root = this->invisibleRootItem();    for (QMap<QString, QCMakePropertyList>::const_iterator iter =           newPropsTree.begin();         iter != newPropsTree.end(); ++iter) {      QString const& key = iter.key();      QCMakePropertyList const& props2 = iter.value();      QList<QStandardItem*> parentItems;      parentItems.append(        new QStandardItem(key.isEmpty() ? tr("Ungrouped Entries") : key));      parentItems.append(new QStandardItem());#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)      parentItems[0]->setData(QBrush(QColor(255, 100, 100)),                              Qt::BackgroundRole);      parentItems[1]->setData(QBrush(QColor(255, 100, 100)),                              Qt::BackgroundRole);#else      parentItems[0]->setData(QBrush(QColor(255, 100, 100)),                              Qt::BackgroundColorRole);      parentItems[1]->setData(QBrush(QColor(255, 100, 100)),                              Qt::BackgroundColorRole);#endif      parentItems[0]->setData(1, GroupRole);      parentItems[1]->setData(1, GroupRole);      root->appendRow(parentItems);      int num = props2.size();      for (int i = 0; i < num; i++) {        QCMakeProperty prop = props2[i];        QList<QStandardItem*> items;        items.append(new QStandardItem());        items.append(new QStandardItem());        parentItems[0]->appendRow(items);        this->setPropertyData(this->indexFromItem(items[0]), prop, true);      }    }    for (QMap<QString, QCMakePropertyList>::const_iterator iter =           newPropsTree2.begin();         iter != newPropsTree2.end(); ++iter) {      QString const& key = iter.key();      QCMakePropertyList const& props2 = iter.value();      QStandardItem* parentItem =        new QStandardItem(key.isEmpty() ? tr("Ungrouped Entries") : key);      root->appendRow(parentItem);      parentItem->setData(1, GroupRole);      int num = props2.size();      for (int i = 0; i < num; i++) {        QCMakeProperty prop = props2[i];        QList<QStandardItem*> items;        items.append(new QStandardItem());        items.append(new QStandardItem());        parentItem->appendRow(items);        this->setPropertyData(this->indexFromItem(items[0]), prop, false);      }    }  }  this->blockSignals(b);  this->endResetModel();}QCMakeCacheModel::ViewType QCMakeCacheModel::viewType() const{  return this->View;}void QCMakeCacheModel::setViewType(QCMakeCacheModel::ViewType t){  this->beginResetModel();  this->View = t;  QCMakePropertyList props = this->properties();  QCMakePropertyList oldProps;  int numNew = this->NewPropertyCount;  int numTotal = props.count();  for (int i = numNew; i < numTotal; i++) {    oldProps.append(props[i]);  }  bool b = this->blockSignals(true);  this->clear();  this->setProperties(oldProps);  this->setProperties(props);  this->blockSignals(b);  this->endResetModel();}void QCMakeCacheModel::setPropertyData(const QModelIndex& idx1,                                       const QCMakeProperty& prop, bool isNew){  QModelIndex idx2 = idx1.sibling(idx1.row(), 1);  this->setData(idx1, prop.Key, Qt::DisplayRole);  this->setData(idx1, prop.Help, QCMakeCacheModel::HelpRole);  this->setData(idx1, prop.Type, QCMakeCacheModel::TypeRole);  this->setData(idx1, prop.Advanced, QCMakeCacheModel::AdvancedRole);  if (prop.Type == QCMakeProperty::BOOL) {    int check = prop.Value.toBool() ? Qt::Checked : Qt::Unchecked;    this->setData(idx2, check, Qt::CheckStateRole);  } else {    this->setData(idx2, prop.Value, Qt::DisplayRole);  }  this->setData(idx2, prop.Help, QCMakeCacheModel::HelpRole);  if (!prop.Strings.isEmpty()) {    this->setData(idx1, prop.Strings, QCMakeCacheModel::StringsRole);  }  if (isNew) {#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)    this->setData(idx1, QBrush(QColor(255, 100, 100)), Qt::BackgroundRole);    this->setData(idx2, QBrush(QColor(255, 100, 100)), Qt::BackgroundRole);#else    this->setData(idx1, QBrush(QColor(255, 100, 100)),                  Qt::BackgroundColorRole);    this->setData(idx2, QBrush(QColor(255, 100, 100)),                  Qt::BackgroundColorRole);#endif  }}void QCMakeCacheModel::getPropertyData(const QModelIndex& idx1,                                       QCMakeProperty& prop) const{  QModelIndex idx2 = idx1.sibling(idx1.row(), 1);  prop.Key = this->data(idx1, Qt::DisplayRole).toString();  prop.Help = this->data(idx1, HelpRole).toString();  prop.Type = static_cast<QCMakeProperty::PropertyType>(    this->data(idx1, TypeRole).toInt());  prop.Advanced = this->data(idx1, AdvancedRole).toBool();  prop.Strings =    this->data(idx1, QCMakeCacheModel::StringsRole).toStringList();  if (prop.Type == QCMakeProperty::BOOL) {    int check = this->data(idx2, Qt::CheckStateRole).toInt();    prop.Value = check == Qt::Checked;  } else {    prop.Value = this->data(idx2, Qt::DisplayRole).toString();  }}QString QCMakeCacheModel::prefix(const QString& s){  QString prefix = s.section('_', 0, 0);  if (prefix == s) {    prefix = QString();  }  return prefix;}void QCMakeCacheModel::breakProperties(  const QSet<QCMakeProperty>& props, QMap<QString, QCMakePropertyList>& result){  QMap<QString, QCMakePropertyList> tmp;  // return a map of properties grouped by prefixes, and sorted  foreach (QCMakeProperty const& p, props) {    QString prefix = QCMakeCacheModel::prefix(p.Key);    tmp[prefix].append(p);  }  // sort it and re-org any properties with only one sub item  QCMakePropertyList reorgProps;  QMap<QString, QCMakePropertyList>::iterator iter;  for (iter = tmp.begin(); iter != tmp.end();) {    if (iter->count() == 1) {      reorgProps.append((*iter)[0]);      iter = tmp.erase(iter);    } else {#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)      std::sort(iter->begin(), iter->end());#else      qSort(*iter);#endif      ++iter;    }  }  if (reorgProps.count()) {    tmp[QString()] += reorgProps;  }  result = tmp;}QCMakePropertyList QCMakeCacheModel::properties() const{  QCMakePropertyList props;  if (!this->rowCount()) {    return props;  }  QVector<QModelIndex> idxs;  idxs.append(this->index(0, 0));  // walk the entire model for property entries  // this works regardless of a flat view or a tree view  while (!idxs.isEmpty()) {    QModelIndex idx = idxs.last();    if (this->hasChildren(idx) && this->rowCount(idx)) {      idxs.append(this->index(0, 0, idx));    } else {      if (!data(idx, GroupRole).toInt()) {        // get data        QCMakeProperty prop;        this->getPropertyData(idx, prop);        props.append(prop);      }      // go to the next in the tree      while (!idxs.isEmpty() &&             (#if QT_VERSION < QT_VERSION_CHECK(5, 1, 0)               (idxs.last().row() + 1) >= rowCount(idxs.last().parent()) ||#endif               !idxs.last().sibling(idxs.last().row() + 1, 0).isValid())) {        idxs.remove(idxs.size() - 1);      }      if (!idxs.isEmpty()) {        idxs.last() = idxs.last().sibling(idxs.last().row() + 1, 0);      }    }  }  return props;}bool QCMakeCacheModel::insertProperty(QCMakeProperty::PropertyType t,                                      const QString& name,                                      const QString& description,                                      const QVariant& value, bool advanced){  QCMakeProperty prop;  prop.Key = name;  prop.Value = value;  prop.Help = description;  prop.Type = t;  prop.Advanced = advanced;  // insert at beginning  this->insertRow(0);  this->setPropertyData(this->index(0, 0), prop, true);  this->NewPropertyCount++;  return true;}void QCMakeCacheModel::setEditEnabled(bool e){  this->EditEnabled = e;}bool QCMakeCacheModel::editEnabled() const{  return this->EditEnabled;}int QCMakeCacheModel::newPropertyCount() const{  return this->NewPropertyCount;}Qt::ItemFlags QCMakeCacheModel::flags(const QModelIndex& idx) const{  Qt::ItemFlags f = QStandardItemModel::flags(idx);  if (!this->EditEnabled) {    f &= ~Qt::ItemIsEditable;    return f;  }  if (QCMakeProperty::BOOL == this->data(idx, TypeRole).toInt()) {    f |= Qt::ItemIsUserCheckable;  }  return f;}QModelIndex QCMakeCacheModel::buddy(const QModelIndex& idx) const{  if (!this->hasChildren(idx) &&      this->data(idx, TypeRole).toInt() != QCMakeProperty::BOOL) {    return this->index(idx.row(), 1, idx.parent());  }  return idx;}QCMakeCacheModelDelegate::QCMakeCacheModelDelegate(QObject* p)  : QItemDelegate(p)  , FileDialogFlag(false){}void QCMakeCacheModelDelegate::setFileDialogFlag(bool f){  this->FileDialogFlag = f;}QWidget* QCMakeCacheModelDelegate::createEditor(  QWidget* p, const QStyleOptionViewItem& /*option*/,  const QModelIndex& idx) const{  QModelIndex var = idx.sibling(idx.row(), 0);  int type = var.data(QCMakeCacheModel::TypeRole).toInt();  if (type == QCMakeProperty::BOOL) {    return nullptr;  }  if (type == QCMakeProperty::PATH) {    QCMakePathEditor* editor =      new QCMakePathEditor(p, var.data(Qt::DisplayRole).toString());    QObject::connect(editor, SIGNAL(fileDialogExists(bool)), this,                     SLOT(setFileDialogFlag(bool)));    return editor;  }  if (type == QCMakeProperty::FILEPATH) {    QCMakeFilePathEditor* editor =      new QCMakeFilePathEditor(p, var.data(Qt::DisplayRole).toString());    QObject::connect(editor, SIGNAL(fileDialogExists(bool)), this,                     SLOT(setFileDialogFlag(bool)));    return editor;  }  if (type == QCMakeProperty::STRING &&      var.data(QCMakeCacheModel::StringsRole).isValid()) {    QCMakeComboBox* editor = new QCMakeComboBox(      p, var.data(QCMakeCacheModel::StringsRole).toStringList());    editor->setFrame(false);    return editor;  }  QLineEdit* editor = new QLineEdit(p);  editor->setFrame(false);  return editor;}bool QCMakeCacheModelDelegate::editorEvent(QEvent* e,                                           QAbstractItemModel* model,                                           const QStyleOptionViewItem& option,                                           const QModelIndex& index){  Qt::ItemFlags flags = model->flags(index);  if (!(flags & Qt::ItemIsUserCheckable) ||      !(option.state & QStyle::State_Enabled) ||      !(flags & Qt::ItemIsEnabled)) {    return false;  }  QVariant value = index.data(Qt::CheckStateRole);  if (!value.isValid()) {    return false;  }  if ((e->type() == QEvent::MouseButtonRelease) ||      (e->type() == QEvent::MouseButtonDblClick)) {    // eat the double click events inside the check rect    if (e->type() == QEvent::MouseButtonDblClick) {      return true;    }  } else if (e->type() == QEvent::KeyPress) {    if (static_cast<QKeyEvent*>(e)->key() != Qt::Key_Space &&        static_cast<QKeyEvent*>(e)->key() != Qt::Key_Select) {      return false;    }  } else {    return false;  }  Qt::CheckState state =    (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked ? Qt::Unchecked                                                               : Qt::Checked);  bool success = model->setData(index, state, Qt::CheckStateRole);  if (success) {    this->recordChange(model, index);  }  return success;}void QCMakeCacheModelDelegate::setModelData(QWidget* editor,                                            QAbstractItemModel* model,                                            const QModelIndex& index) const{  QItemDelegate::setModelData(editor, model, index);  const_cast<QCMakeCacheModelDelegate*>(this)->recordChange(model, index);}QSize QCMakeCacheModelDelegate::sizeHint(const QStyleOptionViewItem& option,                                         const QModelIndex& index) const{  QSize sz = QItemDelegate::sizeHint(option, index);  QStyle* style = QApplication::style();  // increase to checkbox size  QStyleOptionButton opt;  opt.QStyleOption::operator=(option);#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)  sz = sz.expandedTo(    style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &opt, nullptr)      .size());#else  sz = sz.expandedTo(    style->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt, nullptr)      .size());#endif  return sz;}QSet<QCMakeProperty> QCMakeCacheModelDelegate::changes() const{  return mChanges;}void QCMakeCacheModelDelegate::clearChanges(){  mChanges.clear();}void QCMakeCacheModelDelegate::recordChange(QAbstractItemModel* model,                                            const QModelIndex& index){  QModelIndex idx = index;  QAbstractItemModel* mymodel = model;  while (qobject_cast<QAbstractProxyModel*>(mymodel)) {    idx = static_cast<QAbstractProxyModel*>(mymodel)->mapToSource(idx);    mymodel = static_cast<QAbstractProxyModel*>(mymodel)->sourceModel();  }  QCMakeCacheModel* cache_model = qobject_cast<QCMakeCacheModel*>(mymodel);  if (cache_model && idx.isValid()) {    QCMakeProperty prop;    idx = idx.sibling(idx.row(), 0);    cache_model->getPropertyData(idx, prop);    // clean out an old one    QSet<QCMakeProperty>::iterator iter = mChanges.find(prop);    if (iter != mChanges.end()) {      mChanges.erase(iter);    }    // now add the new item    mChanges.insert(prop);  }}
 |