Imported Upstream version 2.8.12.2
[platform/upstream/cmake.git] / Source / QtDialog / QCMakeCacheView.cxx
1 /*============================================================================
2   CMake - Cross Platform Makefile Generator
3   Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
4
5   Distributed under the OSI-approved BSD License (the "License");
6   see accompanying file Copyright.txt for details.
7
8   This software is distributed WITHOUT ANY WARRANTY; without even the
9   implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10   See the License for more information.
11 ============================================================================*/
12
13 #include "QCMakeCacheView.h"
14
15 #include <QHBoxLayout>
16 #include <QHeaderView>
17 #include <QEvent>
18 #include <QStyle>
19 #include <QKeyEvent>
20 #include <QSortFilterProxyModel>
21 #include <QMetaProperty>
22 #include <QApplication>
23
24 #include "QCMakeWidgets.h"
25
26 // filter for searches
27 class QCMakeSearchFilter : public QSortFilterProxyModel
28 {
29 public:
30   QCMakeSearchFilter(QObject* o) : QSortFilterProxyModel(o) {}
31 protected:
32   bool filterAcceptsRow(int row, const QModelIndex& p) const
33     {
34     QStringList strs;
35     const QAbstractItemModel* m = this->sourceModel();
36     QModelIndex idx = m->index(row, 0, p);
37
38     // if there are no children, get strings for column 0 and 1
39     if(!m->hasChildren(idx))
40       {
41       strs.append(m->data(idx).toString());
42       idx = m->index(row, 1, p);
43       strs.append(m->data(idx).toString());
44       }
45     else
46       {
47       // get strings for children entries to compare with
48       // instead of comparing with the parent
49       int num = m->rowCount(idx);
50       for(int i=0; i<num; i++)
51         {
52         QModelIndex tmpidx = m->index(i, 0, idx);
53         strs.append(m->data(tmpidx).toString());
54         tmpidx = m->index(i, 1, idx);
55         strs.append(m->data(tmpidx).toString());
56         }
57       }
58
59     // check all strings for a match
60     foreach(QString str, strs)
61       {
62       if(str.contains(this->filterRegExp()))
63         {
64         return true;
65         }
66       }
67
68     return false;
69     }
70 };
71
72 // filter for searches
73 class QCMakeAdvancedFilter : public QSortFilterProxyModel
74 {
75 public:
76   QCMakeAdvancedFilter(QObject* o)
77     : QSortFilterProxyModel(o), ShowAdvanced(false) {}
78
79   void setShowAdvanced(bool f)
80   {
81     this->ShowAdvanced = f;
82     this->invalidate();
83   }
84   bool showAdvanced() const { return this->ShowAdvanced; }
85
86 protected:
87
88   bool ShowAdvanced;
89
90   bool filterAcceptsRow(int row, const QModelIndex& p) const
91     {
92     const QAbstractItemModel* m = this->sourceModel();
93     QModelIndex idx = m->index(row, 0, p);
94
95     // if there are no children
96     if(!m->hasChildren(idx))
97       {
98       bool adv = m->data(idx, QCMakeCacheModel::AdvancedRole).toBool();
99       if(!adv || (adv && this->ShowAdvanced))
100         {
101         return true;
102         }
103       return false;
104       }
105
106     // check children
107     int num = m->rowCount(idx);
108     for(int i=0; i<num; i++)
109       {
110       bool accept = this->filterAcceptsRow(i, idx);
111       if(accept)
112         {
113         return true;
114         }
115       }
116     return false;
117     }
118 };
119
120 QCMakeCacheView::QCMakeCacheView(QWidget* p)
121   : QTreeView(p)
122 {
123   // hook up our model and search/filter proxies
124   this->CacheModel = new QCMakeCacheModel(this);
125   this->AdvancedFilter = new QCMakeAdvancedFilter(this);
126   this->AdvancedFilter->setSourceModel(this->CacheModel);
127   this->AdvancedFilter->setDynamicSortFilter(true);
128   this->SearchFilter = new QCMakeSearchFilter(this);
129   this->SearchFilter->setSourceModel(this->AdvancedFilter);
130   this->SearchFilter->setFilterCaseSensitivity(Qt::CaseInsensitive);
131   this->SearchFilter->setDynamicSortFilter(true);
132   this->setModel(this->SearchFilter);
133
134   // our delegate for creating our editors
135   QCMakeCacheModelDelegate* delegate = new QCMakeCacheModelDelegate(this);
136   this->setItemDelegate(delegate);
137
138   this->setUniformRowHeights(true);
139
140   this->setEditTriggers(QAbstractItemView::AllEditTriggers);
141
142   // tab, backtab doesn't step through items
143   this->setTabKeyNavigation(false);
144
145   this->setRootIsDecorated(false);
146 }
147
148 bool QCMakeCacheView::event(QEvent* e)
149 {
150   if(e->type() == QEvent::Show)
151     {
152     this->header()->setDefaultSectionSize(this->viewport()->width()/2);
153     }
154   return QTreeView::event(e);
155 }
156
157 QCMakeCacheModel* QCMakeCacheView::cacheModel() const
158 {
159   return this->CacheModel;
160 }
161
162 QModelIndex QCMakeCacheView::moveCursor(CursorAction act,
163   Qt::KeyboardModifiers mod)
164 {
165   // want home/end to go to begin/end of rows, not columns
166   if(act == MoveHome)
167     {
168     return this->model()->index(0, 1);
169     }
170   else if(act == MoveEnd)
171     {
172     return this->model()->index(this->model()->rowCount()-1, 1);
173     }
174   return QTreeView::moveCursor(act, mod);
175 }
176
177 void QCMakeCacheView::setShowAdvanced(bool s)
178 {
179 #if QT_VERSION >= 040300
180   // new 4.3 API that needs to be called.  what about an older Qt?
181   this->SearchFilter->invalidate();
182 #endif
183
184   this->AdvancedFilter->setShowAdvanced(s);
185 }
186
187 bool QCMakeCacheView::showAdvanced() const
188 {
189   return this->AdvancedFilter->showAdvanced();
190 }
191
192 void QCMakeCacheView::setSearchFilter(const QString& s)
193 {
194   this->SearchFilter->setFilterFixedString(s);
195 }
196
197 QCMakeCacheModel::QCMakeCacheModel(QObject* p)
198   : QStandardItemModel(p),
199     EditEnabled(true),
200     NewPropertyCount(0),
201     View(FlatView)
202 {
203   this->ShowNewProperties = true;
204   QStringList labels;
205   labels << tr("Name") << tr("Value");
206   this->setHorizontalHeaderLabels(labels);
207 }
208
209 QCMakeCacheModel::~QCMakeCacheModel()
210 {
211 }
212
213 static uint qHash(const QCMakeProperty& p)
214 {
215   return qHash(p.Key);
216 }
217
218 void QCMakeCacheModel::setShowNewProperties(bool f)
219 {
220   this->ShowNewProperties = f;
221 }
222
223 void QCMakeCacheModel::clear()
224 {
225   this->QStandardItemModel::clear();
226   this->NewPropertyCount = 0;
227
228   QStringList labels;
229   labels << tr("Name") << tr("Value");
230   this->setHorizontalHeaderLabels(labels);
231 }
232
233 void QCMakeCacheModel::setProperties(const QCMakePropertyList& props)
234 {
235   QSet<QCMakeProperty> newProps, newProps2;
236
237   if(this->ShowNewProperties)
238     {
239     newProps = props.toSet();
240     newProps2 = newProps;
241     QSet<QCMakeProperty> oldProps = this->properties().toSet();
242     oldProps.intersect(newProps);
243     newProps.subtract(oldProps);
244     newProps2.subtract(newProps);
245     }
246   else
247     {
248     newProps2 = props.toSet();
249     }
250
251   bool b = this->blockSignals(true);
252
253   this->clear();
254   this->NewPropertyCount = newProps.size();
255
256   if(View == FlatView)
257   {
258     QCMakePropertyList newP = newProps.toList();
259     QCMakePropertyList newP2 = newProps2.toList();
260     qSort(newP);
261     qSort(newP2);
262     int row_count = 0;
263     foreach(QCMakeProperty p, newP)
264     {
265       this->insertRow(row_count);
266       this->setPropertyData(this->index(row_count, 0), p, true);
267       row_count++;
268     }
269     foreach(QCMakeProperty p, newP2)
270     {
271       this->insertRow(row_count);
272       this->setPropertyData(this->index(row_count, 0), p, false);
273       row_count++;
274     }
275   }
276   else if(this->View == GroupView)
277   {
278     QMap<QString, QCMakePropertyList> newPropsTree;
279     this->breakProperties(newProps, newPropsTree);
280     QMap<QString, QCMakePropertyList> newPropsTree2;
281     this->breakProperties(newProps2, newPropsTree2);
282
283     QStandardItem* root = this->invisibleRootItem();
284
285     foreach(QString key, newPropsTree.keys())
286       {
287       QCMakePropertyList props2 = newPropsTree[key];
288
289       QList<QStandardItem*> parentItems;
290       parentItems.append(
291         new QStandardItem(key.isEmpty() ? tr("Ungrouped Entries") : key)
292         );
293       parentItems.append(new QStandardItem());
294       parentItems[0]->setData(QBrush(QColor(255,100,100)), Qt::BackgroundColorRole);
295       parentItems[1]->setData(QBrush(QColor(255,100,100)), Qt::BackgroundColorRole);
296       parentItems[0]->setData(1, GroupRole);
297       parentItems[1]->setData(1, GroupRole);
298       root->appendRow(parentItems);
299
300       int num = props2.size();
301       for(int i=0; i<num; i++)
302         {
303         QCMakeProperty prop = props2[i];
304         QList<QStandardItem*> items;
305         items.append(new QStandardItem());
306         items.append(new QStandardItem());
307         parentItems[0]->appendRow(items);
308         this->setPropertyData(this->indexFromItem(items[0]), prop, true);
309         }
310       }
311
312     foreach(QString key, newPropsTree2.keys())
313       {
314       QCMakePropertyList props2 = newPropsTree2[key];
315
316       QStandardItem* parentItem =
317         new QStandardItem(key.isEmpty() ? tr("Ungrouped Entries") : key);
318       root->appendRow(parentItem);
319       parentItem->setData(1, GroupRole);
320
321       int num = props2.size();
322       for(int i=0; i<num; i++)
323         {
324         QCMakeProperty prop = props2[i];
325         QList<QStandardItem*> items;
326         items.append(new QStandardItem());
327         items.append(new QStandardItem());
328         parentItem->appendRow(items);
329         this->setPropertyData(this->indexFromItem(items[0]), prop, false);
330         }
331       }
332   }
333
334   this->blockSignals(b);
335   this->reset();
336 }
337
338 QCMakeCacheModel::ViewType QCMakeCacheModel::viewType() const
339 {
340   return this->View;
341 }
342
343 void QCMakeCacheModel::setViewType(QCMakeCacheModel::ViewType t)
344 {
345   this->View = t;
346
347   QCMakePropertyList props = this->properties();
348   QCMakePropertyList oldProps;
349   int numNew = this->NewPropertyCount;
350   int numTotal = props.count();
351   for(int i=numNew; i<numTotal; i++)
352   {
353     oldProps.append(props[i]);
354   }
355
356   bool b = this->blockSignals(true);
357   this->clear();
358   this->setProperties(oldProps);
359   this->setProperties(props);
360   this->blockSignals(b);
361   this->reset();
362 }
363
364 void QCMakeCacheModel::setPropertyData(const QModelIndex& idx1,
365     const QCMakeProperty& prop, bool isNew)
366 {
367   QModelIndex idx2 = idx1.sibling(idx1.row(), 1);
368
369   this->setData(idx1, prop.Key, Qt::DisplayRole);
370   this->setData(idx1, prop.Help, QCMakeCacheModel::HelpRole);
371   this->setData(idx1, prop.Type, QCMakeCacheModel::TypeRole);
372   this->setData(idx1, prop.Advanced, QCMakeCacheModel::AdvancedRole);
373
374   if(prop.Type == QCMakeProperty::BOOL)
375   {
376     int check = prop.Value.toBool() ? Qt::Checked : Qt::Unchecked;
377     this->setData(idx2, check, Qt::CheckStateRole);
378   }
379   else
380   {
381     this->setData(idx2, prop.Value, Qt::DisplayRole);
382   }
383   this->setData(idx2, prop.Help, QCMakeCacheModel::HelpRole);
384
385   if (!prop.Strings.isEmpty())
386   {
387     this->setData(idx1, prop.Strings, QCMakeCacheModel::StringsRole);
388   }
389
390   if(isNew)
391   {
392     this->setData(idx1, QBrush(QColor(255,100,100)), Qt::BackgroundColorRole);
393     this->setData(idx2, QBrush(QColor(255,100,100)), Qt::BackgroundColorRole);
394   }
395 }
396
397 void QCMakeCacheModel::getPropertyData(const QModelIndex& idx1,
398     QCMakeProperty& prop) const
399 {
400   QModelIndex idx2 = idx1.sibling(idx1.row(), 1);
401
402   prop.Key = this->data(idx1, Qt::DisplayRole).toString();
403   prop.Help = this->data(idx1, HelpRole).toString();
404   prop.Type = static_cast<QCMakeProperty::PropertyType>(this->data(idx1, TypeRole).toInt());
405   prop.Advanced = this->data(idx1, AdvancedRole).toBool();
406   prop.Strings = this->data(idx1, QCMakeCacheModel::StringsRole).toStringList();
407   if(prop.Type == QCMakeProperty::BOOL)
408   {
409     int check = this->data(idx2, Qt::CheckStateRole).toInt();
410     prop.Value = check == Qt::Checked;
411   }
412   else
413   {
414     prop.Value = this->data(idx2, Qt::DisplayRole).toString();
415   }
416 }
417
418 QString QCMakeCacheModel::prefix(const QString& s)
419 {
420   QString prefix = s.section('_', 0, 0);
421   if(prefix == s)
422     {
423     prefix = QString();
424     }
425   return prefix;
426 }
427
428 void QCMakeCacheModel::breakProperties(const QSet<QCMakeProperty>& props,
429                      QMap<QString, QCMakePropertyList>& result)
430 {
431   QMap<QString, QCMakePropertyList> tmp;
432   // return a map of properties grouped by prefixes, and sorted
433   foreach(QCMakeProperty p, props)
434     {
435     QString prefix = QCMakeCacheModel::prefix(p.Key);
436     tmp[prefix].append(p);
437     }
438   // sort it and re-org any properties with only one sub item
439   QCMakePropertyList reorgProps;
440   QMap<QString, QCMakePropertyList>::iterator iter;
441   for(iter = tmp.begin(); iter != tmp.end();)
442     {
443     if(iter->count() == 1)
444       {
445       reorgProps.append((*iter)[0]);
446       iter = tmp.erase(iter);
447       }
448     else
449       {
450       qSort(*iter);
451       ++iter;
452       }
453     }
454   if(reorgProps.count())
455     {
456     tmp[QString()] += reorgProps;
457     }
458   result = tmp;
459 }
460
461 QCMakePropertyList QCMakeCacheModel::properties() const
462 {
463   QCMakePropertyList props;
464
465   if(!this->rowCount())
466     {
467     return props;
468     }
469
470   QList<QModelIndex> idxs;
471   idxs.append(this->index(0,0));
472
473   // walk the entire model for property entries
474   // this works regardless of a flat view or a tree view
475   while(!idxs.isEmpty())
476   {
477     QModelIndex idx = idxs.last();
478     if(this->hasChildren(idx) && this->rowCount(idx))
479     {
480       idxs.append(this->index(0,0, idx));
481     }
482     else
483     {
484       if(!data(idx, GroupRole).toInt())
485       {
486         // get data
487         QCMakeProperty prop;
488         this->getPropertyData(idx, prop);
489         props.append(prop);
490       }
491
492       // go to the next in the tree
493       while(!idxs.isEmpty() && (
494 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) && QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
495         (idxs.last().row()+1) >= rowCount(idxs.last().parent()) ||
496 #endif
497         !idxs.last().sibling(idxs.last().row()+1, 0).isValid()))
498       {
499         idxs.removeLast();
500       }
501       if(!idxs.isEmpty())
502       {
503         idxs.last() = idxs.last().sibling(idxs.last().row()+1, 0);
504       }
505     }
506   }
507
508   return props;
509 }
510
511 bool QCMakeCacheModel::insertProperty(QCMakeProperty::PropertyType t,
512                       const QString& name, const QString& description,
513                       const QVariant& value, bool advanced)
514 {
515   QCMakeProperty prop;
516   prop.Key = name;
517   prop.Value = value;
518   prop.Help = description;
519   prop.Type = t;
520   prop.Advanced = advanced;
521
522   //insert at beginning
523   this->insertRow(0);
524   this->setPropertyData(this->index(0,0), prop, true);
525   this->NewPropertyCount++;
526   return true;
527 }
528
529 void QCMakeCacheModel::setEditEnabled(bool e)
530 {
531   this->EditEnabled = e;
532 }
533
534 bool QCMakeCacheModel::editEnabled() const
535 {
536   return this->EditEnabled;
537 }
538
539 int QCMakeCacheModel::newPropertyCount() const
540 {
541   return this->NewPropertyCount;
542 }
543
544 Qt::ItemFlags QCMakeCacheModel::flags (const QModelIndex& idx) const
545 {
546   Qt::ItemFlags f = QStandardItemModel::flags(idx);
547   if(!this->EditEnabled)
548     {
549     f &= ~Qt::ItemIsEditable;
550     return f;
551     }
552   if(QCMakeProperty::BOOL == this->data(idx, TypeRole).toInt())
553     {
554     f |= Qt::ItemIsUserCheckable;
555     }
556   return f;
557 }
558
559 QModelIndex QCMakeCacheModel::buddy(const QModelIndex& idx) const
560 {
561   if(!this->hasChildren(idx) &&
562      this->data(idx, TypeRole).toInt() != QCMakeProperty::BOOL)
563   {
564     return this->index(idx.row(), 1, idx.parent());
565   }
566   return idx;
567 }
568
569 QCMakeCacheModelDelegate::QCMakeCacheModelDelegate(QObject* p)
570   : QItemDelegate(p), FileDialogFlag(false)
571 {
572 }
573
574 void QCMakeCacheModelDelegate::setFileDialogFlag(bool f)
575 {
576   this->FileDialogFlag = f;
577 }
578
579 QWidget* QCMakeCacheModelDelegate::createEditor(QWidget* p,
580     const QStyleOptionViewItem&, const QModelIndex& idx) const
581 {
582   QModelIndex var = idx.sibling(idx.row(), 0);
583   int type = var.data(QCMakeCacheModel::TypeRole).toInt();
584   if(type == QCMakeProperty::BOOL)
585     {
586     return NULL;
587     }
588   else if(type == QCMakeProperty::PATH)
589     {
590     QCMakePathEditor* editor =
591       new QCMakePathEditor(p,
592       var.data(Qt::DisplayRole).toString());
593     QObject::connect(editor, SIGNAL(fileDialogExists(bool)), this,
594         SLOT(setFileDialogFlag(bool)));
595     return editor;
596     }
597   else if(type == QCMakeProperty::FILEPATH)
598     {
599     QCMakeFilePathEditor* editor =
600       new QCMakeFilePathEditor(p,
601       var.data(Qt::DisplayRole).toString());
602     QObject::connect(editor, SIGNAL(fileDialogExists(bool)), this,
603         SLOT(setFileDialogFlag(bool)));
604     return editor;
605     }
606   else if(type == QCMakeProperty::STRING &&
607           var.data(QCMakeCacheModel::StringsRole).isValid())
608     {
609     QCMakeComboBox* editor =
610       new QCMakeComboBox(p, var.data(QCMakeCacheModel::StringsRole).toStringList());
611     editor->setFrame(false);
612     return editor;
613     }
614
615   QLineEdit* editor = new QLineEdit(p);
616   editor->setFrame(false);
617   return editor;
618 }
619
620 bool QCMakeCacheModelDelegate::editorEvent(QEvent* e, QAbstractItemModel* model,
621        const QStyleOptionViewItem& option, const QModelIndex& index)
622 {
623   Qt::ItemFlags flags = model->flags(index);
624   if (!(flags & Qt::ItemIsUserCheckable) || !(option.state & QStyle::State_Enabled)
625       || !(flags & Qt::ItemIsEnabled))
626     {
627     return false;
628     }
629
630   QVariant value = index.data(Qt::CheckStateRole);
631   if (!value.isValid())
632     {
633     return false;
634     }
635
636   if ((e->type() == QEvent::MouseButtonRelease)
637       || (e->type() == QEvent::MouseButtonDblClick))
638     {
639     // eat the double click events inside the check rect
640     if (e->type() == QEvent::MouseButtonDblClick)
641       {
642       return true;
643       }
644     }
645   else if (e->type() == QEvent::KeyPress)
646     {
647     if(static_cast<QKeyEvent*>(e)->key() != Qt::Key_Space &&
648        static_cast<QKeyEvent*>(e)->key() != Qt::Key_Select)
649       {
650       return false;
651       }
652     }
653   else
654     {
655     return false;
656     }
657
658   Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked
659                           ? Qt::Unchecked : Qt::Checked);
660   bool success = model->setData(index, state, Qt::CheckStateRole);
661   if(success)
662     {
663     this->recordChange(model, index);
664     }
665   return success;
666 }
667
668 // Issue 205903 fixed in Qt 4.5.0.
669 // Can remove this function and FileDialogFlag when minimum Qt version is 4.5
670 bool QCMakeCacheModelDelegate::eventFilter(QObject* object, QEvent* evt)
671 {
672   // workaround for what looks like a bug in Qt on Mac OS X
673   // where it doesn't create a QWidget wrapper for the native file dialog
674   // so the Qt library ends up assuming the focus was lost to something else
675
676   if(evt->type() == QEvent::FocusOut && this->FileDialogFlag)
677     {
678     return false;
679     }
680   return QItemDelegate::eventFilter(object, evt);
681 }
682
683 void QCMakeCacheModelDelegate::setModelData(QWidget* editor,
684   QAbstractItemModel* model, const QModelIndex& index ) const
685 {
686   QItemDelegate::setModelData(editor, model, index);
687   const_cast<QCMakeCacheModelDelegate*>(this)->recordChange(model, index);
688 }
689
690 QSize QCMakeCacheModelDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
691 {
692   QSize sz = QItemDelegate::sizeHint(option, index);
693   QStyle *style = QApplication::style();
694
695   // increase to checkbox size
696   QStyleOptionButton opt;
697   opt.QStyleOption::operator=(option);
698   sz = sz.expandedTo(style->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt, NULL).size());
699
700   return sz;
701 }
702
703 QSet<QCMakeProperty> QCMakeCacheModelDelegate::changes() const
704 {
705   return mChanges;
706 }
707
708 void QCMakeCacheModelDelegate::clearChanges()
709 {
710   mChanges.clear();
711 }
712
713 void QCMakeCacheModelDelegate::recordChange(QAbstractItemModel* model, const QModelIndex& index)
714 {
715   QModelIndex idx = index;
716   QAbstractItemModel* mymodel = model;
717   while(qobject_cast<QAbstractProxyModel*>(mymodel))
718     {
719     idx = static_cast<QAbstractProxyModel*>(mymodel)->mapToSource(idx);
720     mymodel = static_cast<QAbstractProxyModel*>(mymodel)->sourceModel();
721     }
722   QCMakeCacheModel* cache_model = qobject_cast<QCMakeCacheModel*>(mymodel);
723   if(cache_model && idx.isValid())
724     {
725     QCMakeProperty prop;
726     idx = idx.sibling(idx.row(), 0);
727     cache_model->getPropertyData(idx, prop);
728
729     // clean out an old one
730     QSet<QCMakeProperty>::iterator iter = mChanges.find(prop);
731     if(iter != mChanges.end())
732       {
733       mChanges.erase(iter);
734       }
735     // now add the new item
736     mChanges.insert(prop);
737     }
738 }
739