1 /*=========================================================================
3 Program: CMake - Cross-Platform Makefile Generator
4 Module: $RCSfile: QCMakeCacheView.cxx,v $
6 Date: $Date: 2009-08-10 17:25:22 $
7 Version: $Revision: 1.42 $
9 Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved.
10 See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
12 This software is distributed WITHOUT ANY WARRANTY; without even
13 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 PURPOSE. See the above copyright notices for more information.
16 =========================================================================*/
18 #include "QCMakeCacheView.h"
20 #include <QHBoxLayout>
21 #include <QHeaderView>
25 #include <QSortFilterProxyModel>
26 #include <QMetaProperty>
27 #include <QApplication>
29 #include "QCMakeWidgets.h"
31 // filter for searches
32 class QCMakeSearchFilter
: public QSortFilterProxyModel
35 QCMakeSearchFilter(QObject
* o
) : QSortFilterProxyModel(o
) {}
37 bool filterAcceptsRow(int row
, const QModelIndex
& p
) const
40 const QAbstractItemModel
* m
= this->sourceModel();
41 QModelIndex idx
= m
->index(row
, 0, p
);
43 // if there are no children, get strings for column 0 and 1
44 if(!m
->hasChildren(idx
))
46 strs
.append(m
->data(idx
).toString());
47 idx
= m
->index(row
, 1, p
);
48 strs
.append(m
->data(idx
).toString());
52 // get strings for children entries to compare with
53 // instead of comparing with the parent
54 int num
= m
->rowCount(idx
);
55 for(int i
=0; i
<num
; i
++)
57 QModelIndex tmpidx
= m
->index(i
, 0, idx
);
58 strs
.append(m
->data(tmpidx
).toString());
59 tmpidx
= m
->index(i
, 1, idx
);
60 strs
.append(m
->data(tmpidx
).toString());
64 // check all strings for a match
65 foreach(QString str
, strs
)
67 if(str
.contains(this->filterRegExp()))
77 // filter for searches
78 class QCMakeAdvancedFilter
: public QSortFilterProxyModel
81 QCMakeAdvancedFilter(QObject
* o
)
82 : QSortFilterProxyModel(o
), ShowAdvanced(false) {}
84 void setShowAdvanced(bool f
)
86 this->ShowAdvanced
= f
;
89 bool showAdvanced() const { return this->ShowAdvanced
; }
95 bool filterAcceptsRow(int row
, const QModelIndex
& p
) const
97 const QAbstractItemModel
* m
= this->sourceModel();
98 QModelIndex idx
= m
->index(row
, 0, p
);
100 // if there are no children
101 if(!m
->hasChildren(idx
))
103 bool adv
= m
->data(idx
, QCMakeCacheModel::AdvancedRole
).toBool();
104 if(!adv
|| (adv
&& this->ShowAdvanced
))
112 int num
= m
->rowCount(idx
);
113 for(int i
=0; i
<num
; i
++)
115 bool accept
= this->filterAcceptsRow(i
, idx
);
125 QCMakeCacheView::QCMakeCacheView(QWidget
* p
)
128 // hook up our model and search/filter proxies
129 this->CacheModel
= new QCMakeCacheModel(this);
130 this->AdvancedFilter
= new QCMakeAdvancedFilter(this);
131 this->AdvancedFilter
->setSourceModel(this->CacheModel
);
132 this->AdvancedFilter
->setDynamicSortFilter(true);
133 this->SearchFilter
= new QCMakeSearchFilter(this);
134 this->SearchFilter
->setSourceModel(this->AdvancedFilter
);
135 this->SearchFilter
->setFilterCaseSensitivity(Qt::CaseInsensitive
);
136 this->SearchFilter
->setDynamicSortFilter(true);
137 this->setModel(this->SearchFilter
);
139 // our delegate for creating our editors
140 QCMakeCacheModelDelegate
* delegate
= new QCMakeCacheModelDelegate(this);
141 this->setItemDelegate(delegate
);
143 this->setUniformRowHeights(true);
145 this->setEditTriggers(QAbstractItemView::AllEditTriggers
);
147 // tab, backtab doesn't step through items
148 this->setTabKeyNavigation(false);
150 this->setRootIsDecorated(false);
153 bool QCMakeCacheView::event(QEvent
* e
)
155 if(e
->type() == QEvent::Show
)
157 this->header()->setDefaultSectionSize(this->viewport()->width()/2);
159 return QTreeView::event(e
);
162 QCMakeCacheModel
* QCMakeCacheView::cacheModel() const
164 return this->CacheModel
;
167 QModelIndex
QCMakeCacheView::moveCursor(CursorAction act
,
168 Qt::KeyboardModifiers mod
)
170 // want home/end to go to begin/end of rows, not columns
173 return this->model()->index(0, 1);
175 else if(act
== MoveEnd
)
177 return this->model()->index(this->model()->rowCount()-1, 1);
179 return QTreeView::moveCursor(act
, mod
);
182 void QCMakeCacheView::setShowAdvanced(bool s
)
184 #if QT_VERSION >= 040300
185 // new 4.3 api that needs to be called. what about an older Qt?
186 this->SearchFilter
->invalidate();
189 this->AdvancedFilter
->setShowAdvanced(s
);
192 bool QCMakeCacheView::showAdvanced() const
194 return this->AdvancedFilter
->showAdvanced();
197 void QCMakeCacheView::setSearchFilter(const QString
& s
)
199 this->SearchFilter
->setFilterFixedString(s
);
202 QCMakeCacheModel::QCMakeCacheModel(QObject
* p
)
203 : QStandardItemModel(p
),
209 labels
<< tr("Name") << tr("Value");
210 this->setHorizontalHeaderLabels(labels
);
213 QCMakeCacheModel::~QCMakeCacheModel()
217 static uint
qHash(const QCMakeProperty
& p
)
222 void QCMakeCacheModel::clear()
224 this->QStandardItemModel::clear();
225 this->NewPropertyCount
= 0;
228 labels
<< tr("Name") << tr("Value");
229 this->setHorizontalHeaderLabels(labels
);
232 void QCMakeCacheModel::setProperties(const QCMakePropertyList
& props
)
234 QSet
<QCMakeProperty
> newProps
= props
.toSet();
235 QSet
<QCMakeProperty
> newProps2
= newProps
;
236 QSet
<QCMakeProperty
> oldProps
= this->properties().toSet();
238 oldProps
.intersect(newProps
);
239 newProps
.subtract(oldProps
);
240 newProps2
.subtract(newProps
);
242 bool b
= this->blockSignals(true);
245 this->NewPropertyCount
= newProps
.size();
249 QCMakePropertyList newP
= newProps
.toList();
250 QCMakePropertyList newP2
= newProps2
.toList();
254 foreach(QCMakeProperty p
, newP
)
256 this->insertRow(rowCount
);
257 this->setPropertyData(this->index(rowCount
, 0), p
, true);
260 foreach(QCMakeProperty p
, newP2
)
262 this->insertRow(rowCount
);
263 this->setPropertyData(this->index(rowCount
, 0), p
, false);
267 else if(this->View
== GroupView
)
269 QMap
<QString
, QCMakePropertyList
> newPropsTree
;
270 this->breakProperties(newProps
, newPropsTree
);
271 QMap
<QString
, QCMakePropertyList
> newPropsTree2
;
272 this->breakProperties(newProps2
, newPropsTree2
);
274 QStandardItem
* root
= this->invisibleRootItem();
276 foreach(QString key
, newPropsTree
.keys())
278 QCMakePropertyList props
= newPropsTree
[key
];
280 QList
<QStandardItem
*> parentItems
;
282 new QStandardItem(key
.isEmpty() ? tr("Ungrouped Entries") : key
)
284 parentItems
.append(new QStandardItem());
285 parentItems
[0]->setData(QBrush(QColor(255,100,100)), Qt::BackgroundColorRole
);
286 parentItems
[1]->setData(QBrush(QColor(255,100,100)), Qt::BackgroundColorRole
);
287 root
->appendRow(parentItems
);
289 foreach(QCMakeProperty prop
, props
)
291 QList
<QStandardItem
*> items
;
292 items
.append(new QStandardItem());
293 items
.append(new QStandardItem());
294 parentItems
[0]->appendRow(items
);
295 this->setPropertyData(this->indexFromItem(items
[0]), prop
, true);
299 foreach(QString key
, newPropsTree2
.keys())
301 QCMakePropertyList props
= newPropsTree2
[key
];
303 QStandardItem
* parentItem
=
304 new QStandardItem(key
.isEmpty() ? tr("Ungrouped Entries") : key
);
305 root
->appendRow(parentItem
);
307 foreach(QCMakeProperty prop
, props
)
309 QList
<QStandardItem
*> items
;
310 items
.append(new QStandardItem());
311 items
.append(new QStandardItem());
312 parentItem
->appendRow(items
);
313 this->setPropertyData(this->indexFromItem(items
[0]), prop
, false);
318 this->blockSignals(b
);
322 QCMakeCacheModel::ViewType
QCMakeCacheModel::viewType() const
327 void QCMakeCacheModel::setViewType(QCMakeCacheModel::ViewType t
)
331 QCMakePropertyList props
= this->properties();
332 QCMakePropertyList oldProps
;
333 int numNew
= this->NewPropertyCount
;
334 int numTotal
= props
.count();
335 for(int i
=numNew
; i
<numTotal
; i
++)
337 oldProps
.append(props
[i
]);
340 bool b
= this->blockSignals(true);
342 this->setProperties(oldProps
);
343 this->setProperties(props
);
344 this->blockSignals(b
);
348 void QCMakeCacheModel::setPropertyData(const QModelIndex
& idx1
,
349 const QCMakeProperty
& prop
, bool isNew
)
351 QModelIndex idx2
= idx1
.sibling(idx1
.row(), 1);
353 this->setData(idx1
, prop
.Key
, Qt::DisplayRole
);
354 this->setData(idx1
, prop
.Help
, QCMakeCacheModel::HelpRole
);
355 this->setData(idx1
, prop
.Type
, QCMakeCacheModel::TypeRole
);
356 this->setData(idx1
, prop
.Advanced
, QCMakeCacheModel::AdvancedRole
);
358 if(prop
.Type
== QCMakeProperty::BOOL
)
360 int check
= prop
.Value
.toBool() ? Qt::Checked
: Qt::Unchecked
;
361 this->setData(idx2
, check
, Qt::CheckStateRole
);
365 this->setData(idx2
, prop
.Value
, Qt::DisplayRole
);
367 this->setData(idx2
, prop
.Help
, QCMakeCacheModel::HelpRole
);
369 if (!prop
.Strings
.isEmpty())
371 this->setData(idx1
, prop
.Strings
, QCMakeCacheModel::StringsRole
);
376 this->setData(idx1
, QBrush(QColor(255,100,100)), Qt::BackgroundColorRole
);
377 this->setData(idx2
, QBrush(QColor(255,100,100)), Qt::BackgroundColorRole
);
381 void QCMakeCacheModel::getPropertyData(const QModelIndex
& idx1
,
382 QCMakeProperty
& prop
) const
384 QModelIndex idx2
= idx1
.sibling(idx1
.row(), 1);
386 prop
.Key
= this->data(idx1
, Qt::DisplayRole
).toString();
387 prop
.Help
= this->data(idx1
, HelpRole
).toString();
388 prop
.Type
= static_cast<QCMakeProperty::PropertyType
>(this->data(idx1
, TypeRole
).toInt());
389 prop
.Advanced
= this->data(idx1
, AdvancedRole
).toBool();
390 prop
.Strings
= this->data(idx1
, QCMakeCacheModel::StringsRole
).toStringList();
391 if(prop
.Type
== QCMakeProperty::BOOL
)
393 int check
= this->data(idx2
, Qt::CheckStateRole
).toInt();
394 prop
.Value
= check
== Qt::Checked
;
398 prop
.Value
= this->data(idx2
, Qt::DisplayRole
).toString();
402 QString
QCMakeCacheModel::prefix(const QString
& s
)
404 QString prefix
= s
.section('_', 0, 0);
412 void QCMakeCacheModel::breakProperties(const QSet
<QCMakeProperty
>& props
,
413 QMap
<QString
, QCMakePropertyList
>& result
)
415 QMap
<QString
, QCMakePropertyList
> tmp
;
416 // return a map of properties grouped by prefixes, and sorted
417 foreach(QCMakeProperty p
, props
)
419 QString prefix
= QCMakeCacheModel::prefix(p
.Key
);
420 tmp
[prefix
].append(p
);
422 // sort it and re-org any properties with only one sub item
423 QCMakePropertyList reorgProps
;
424 QMap
<QString
, QCMakePropertyList
>::iterator iter
;
425 for(iter
= tmp
.begin(); iter
!= tmp
.end();)
427 if(iter
->count() == 1)
429 reorgProps
.append((*iter
)[0]);
430 iter
= tmp
.erase(iter
);
438 if(reorgProps
.count())
440 tmp
[QString()] += reorgProps
;
445 QCMakePropertyList
QCMakeCacheModel::properties() const
447 QCMakePropertyList props
;
449 if(!this->rowCount())
454 QList
<QModelIndex
> idxs
;
455 idxs
.append(this->index(0,0));
457 // walk the entire model for property entries
458 // this works regardless of a flat view or a tree view
459 while(!idxs
.isEmpty())
461 QModelIndex idx
= idxs
.last();
462 if(this->hasChildren(idx
) && this->rowCount(idx
))
464 idxs
.append(this->index(0,0, idx
));
470 this->getPropertyData(idx
, prop
);
473 // go to the next in the tree
474 while(!idxs
.isEmpty() && !idxs
.last().sibling(idxs
.last().row()+1, 0).isValid())
480 idxs
.last() = idxs
.last().sibling(idxs
.last().row()+1, 0);
488 bool QCMakeCacheModel::insertProperty(QCMakeProperty::PropertyType t
,
489 const QString
& name
, const QString
& description
,
490 const QVariant
& value
, bool advanced
)
495 prop
.Help
= description
;
497 prop
.Advanced
= advanced
;
499 //insert at beginning
501 this->setPropertyData(this->index(0,0), prop
, true);
502 this->NewPropertyCount
++;
506 void QCMakeCacheModel::setEditEnabled(bool e
)
508 this->EditEnabled
= e
;
511 bool QCMakeCacheModel::editEnabled() const
513 return this->EditEnabled
;
516 int QCMakeCacheModel::newPropertyCount() const
518 return this->NewPropertyCount
;
521 Qt::ItemFlags
QCMakeCacheModel::flags (const QModelIndex
& idx
) const
523 Qt::ItemFlags f
= QStandardItemModel::flags(idx
);
524 if(!this->EditEnabled
)
526 f
&= ~Qt::ItemIsEditable
;
529 if(QCMakeProperty::BOOL
== this->data(idx
, TypeRole
).toInt())
531 f
|= Qt::ItemIsUserCheckable
;
536 QModelIndex
QCMakeCacheModel::buddy(const QModelIndex
& idx
) const
538 if(!this->hasChildren(idx
) &&
539 this->data(idx
, TypeRole
).toInt() != QCMakeProperty::BOOL
)
541 return this->index(idx
.row(), 1, idx
.parent());
546 QCMakeCacheModelDelegate::QCMakeCacheModelDelegate(QObject
* p
)
547 : QItemDelegate(p
), FileDialogFlag(false)
551 void QCMakeCacheModelDelegate::setFileDialogFlag(bool f
)
553 this->FileDialogFlag
= f
;
556 QWidget
* QCMakeCacheModelDelegate::createEditor(QWidget
* p
,
557 const QStyleOptionViewItem
&, const QModelIndex
& idx
) const
559 QModelIndex var
= idx
.sibling(idx
.row(), 0);
560 int type
= var
.data(QCMakeCacheModel::TypeRole
).toInt();
561 if(type
== QCMakeProperty::BOOL
)
565 else if(type
== QCMakeProperty::PATH
)
567 QCMakePathEditor
* editor
=
568 new QCMakePathEditor(p
,
569 var
.data(Qt::DisplayRole
).toString());
570 QObject::connect(editor
, SIGNAL(fileDialogExists(bool)), this,
571 SLOT(setFileDialogFlag(bool)));
574 else if(type
== QCMakeProperty::FILEPATH
)
576 QCMakeFilePathEditor
* editor
=
577 new QCMakeFilePathEditor(p
,
578 var
.data(Qt::DisplayRole
).toString());
579 QObject::connect(editor
, SIGNAL(fileDialogExists(bool)), this,
580 SLOT(setFileDialogFlag(bool)));
583 else if(type
== QCMakeProperty::STRING
&&
584 var
.data(QCMakeCacheModel::StringsRole
).isValid())
586 QCMakeComboBox
* editor
=
587 new QCMakeComboBox(p
, var
.data(QCMakeCacheModel::StringsRole
).toStringList());
588 editor
->setFrame(false);
592 QLineEdit
* editor
= new QLineEdit(p
);
593 editor
->setFrame(false);
597 bool QCMakeCacheModelDelegate::editorEvent(QEvent
* e
, QAbstractItemModel
* model
,
598 const QStyleOptionViewItem
& option
, const QModelIndex
& index
)
600 Qt::ItemFlags flags
= model
->flags(index
);
601 if (!(flags
& Qt::ItemIsUserCheckable
) || !(option
.state
& QStyle::State_Enabled
)
602 || !(flags
& Qt::ItemIsEnabled
))
607 QVariant value
= index
.data(Qt::CheckStateRole
);
608 if (!value
.isValid())
613 if ((e
->type() == QEvent::MouseButtonRelease
)
614 || (e
->type() == QEvent::MouseButtonDblClick
))
616 // eat the double click events inside the check rect
617 if (e
->type() == QEvent::MouseButtonDblClick
)
622 else if (e
->type() == QEvent::KeyPress
)
624 if(static_cast<QKeyEvent
*>(e
)->key() != Qt::Key_Space
&&
625 static_cast<QKeyEvent
*>(e
)->key() != Qt::Key_Select
)
635 Qt::CheckState state
= (static_cast<Qt::CheckState
>(value
.toInt()) == Qt::Checked
636 ? Qt::Unchecked
: Qt::Checked
);
637 bool success
= model
->setData(index
, state
, Qt::CheckStateRole
);
640 this->recordChange(model
, index
);
645 // Issue 205903 fixed in Qt 4.5.0.
646 // Can remove this function and FileDialogFlag when minimum Qt version is 4.5
647 bool QCMakeCacheModelDelegate::eventFilter(QObject
* object
, QEvent
* event
)
649 // workaround for what looks like a bug in Qt on Mac OS X
650 // where it doesn't create a QWidget wrapper for the native file dialog
651 // so the Qt library ends up assuming the focus was lost to something else
653 if(event
->type() == QEvent::FocusOut
&& this->FileDialogFlag
)
657 return QItemDelegate::eventFilter(object
, event
);
660 void QCMakeCacheModelDelegate::setModelData(QWidget
* editor
,
661 QAbstractItemModel
* model
, const QModelIndex
& index
) const
663 QItemDelegate::setModelData(editor
, model
, index
);
664 const_cast<QCMakeCacheModelDelegate
*>(this)->recordChange(model
, index
);
667 QSize
QCMakeCacheModelDelegate::sizeHint(const QStyleOptionViewItem
& option
, const QModelIndex
& index
) const
669 QSize sz
= QItemDelegate::sizeHint(option
, index
);
670 QStyle
*style
= QApplication::style();
672 // increase to checkbox size
673 QStyleOptionButton opt
;
674 opt
.QStyleOption::operator=(option
);
675 sz
= sz
.expandedTo(style
->subElementRect(QStyle::SE_ViewItemCheckIndicator
, &opt
, NULL
).size());
680 QSet
<QCMakeProperty
> QCMakeCacheModelDelegate::changes() const
685 void QCMakeCacheModelDelegate::clearChanges()
690 void QCMakeCacheModelDelegate::recordChange(QAbstractItemModel
* model
, const QModelIndex
& index
)
692 QModelIndex idx
= index
;
693 QAbstractItemModel
* mymodel
= model
;
694 while(qobject_cast
<QAbstractProxyModel
*>(mymodel
))
696 idx
= static_cast<QAbstractProxyModel
*>(mymodel
)->mapToSource(idx
);
697 mymodel
= static_cast<QAbstractProxyModel
*>(mymodel
)->sourceModel();
699 QCMakeCacheModel
* cache_model
= qobject_cast
<QCMakeCacheModel
*>(mymodel
);
700 if(cache_model
&& idx
.isValid())
702 QCMakeProperty property
;
703 idx
= idx
.sibling(idx
.row(), 0);
704 cache_model
->getPropertyData(idx
, property
);
706 // clean out an old one
707 QSet
<QCMakeProperty
>::iterator iter
= mChanges
.find(property
);
708 if(iter
!= mChanges
.end())
710 mChanges
.erase(iter
);
712 // now add the new item
713 mChanges
.insert(property
);