diff --git a/share/qtcreator/welcomescreen/widgets/ExampleBrowser.qml b/share/qtcreator/welcomescreen/widgets/ExampleBrowser.qml index c81c6e31a164380c112b9e67820a050c14273045..87c742be2378129fd2f00d04894aaf0d1c4afcf8 100644 --- a/share/qtcreator/welcomescreen/widgets/ExampleBrowser.qml +++ b/share/qtcreator/welcomescreen/widgets/ExampleBrowser.qml @@ -36,10 +36,18 @@ import widgets 1.0 as Widgets Item { id: exampleBrowserRoot + function appendTag(tag) { + var tagStr = "tag:" + '"' + tag + '"' + if (lineEdit.text == "") + lineEdit.text = tagStr + else + lineEdit.text += " " + tagStr + } + Rectangle { - color:"#f4f4f4" id : lineEditRoot + color:"#f4f4f4" width: parent.width height: lineEdit.height + 6 anchors.bottom: parent.bottom @@ -50,14 +58,14 @@ Item { anchors.rightMargin: scrollArea.verticalScrollBar.visible ? 0 : -8 Widgets.LineEdit { + id: lineEdit placeholderText: !checkBox.checked ? qsTr("Search in Tutorials") : qsTr("Search in Tutorials, Examples and Demos") focus: true - id: lineEdit anchors.left: parent.left anchors.leftMargin:4 anchors.verticalCenter: parent.verticalCenter width: Math.max(lineEditRoot.width - checkBox.width - 28 - tagFilterButton.width, 100) - onTextChanged: examplesModel.filterRegExp = RegExp('.*'+text, "im") + onTextChanged: examplesModel.parseSearchString(text) } CheckBox { @@ -74,12 +82,12 @@ Item { Button { id: tagFilterButton property string tag - onTagChanged: { examplesModel.filterTag = tag; examplesModel.updateFilter() } + onTagChanged: exampleBrowserRoot.appendTag(tag) anchors.left: checkBox.right anchors.leftMargin: 6 anchors.verticalCenter: lineEdit.verticalCenter visible: !examplesModel.showTutorialsOnly - text: tag === "" ? qsTr("Filter by Tag") : qsTr("Tag Filter: %1").arg(tag) + text: qsTr("Tag List") onClicked: { tagBrowserLoader.source = "TagBrowser.qml" tagBrowserLoader.item.visible = true @@ -96,7 +104,7 @@ Item { Repeater { id: repeater model: examplesModel - delegate: ExampleDelegate { width: scrollArea.width } + delegate: ExampleDelegate { width: scrollArea.width; onTagClicked: exampleBrowserRoot.appendTag(tag) } } } Component.onCompleted: verticalScrollBar.anchors.bottomMargin = -(scrollArea.anchors.bottomMargin + 8) diff --git a/share/qtcreator/welcomescreen/widgets/ExampleDelegate.qml b/share/qtcreator/welcomescreen/widgets/ExampleDelegate.qml index 8aa5d48893ccfaecb59c5393d19a6bfc72a7d48a..21106ca15e5df741ae346d286e2ffb829b8eb90b 100644 --- a/share/qtcreator/welcomescreen/widgets/ExampleDelegate.qml +++ b/share/qtcreator/welcomescreen/widgets/ExampleDelegate.qml @@ -37,6 +37,8 @@ Rectangle { id: root height: Math.max(image.height-20, description.paintedHeight) + 68 color: "#00ffffff" + property variant tags : model.tags + signal tagClicked(string tag) Components.QStyleItem { cursor: "pointinghandcursor" ; anchors.fill: parent } @@ -96,17 +98,6 @@ Rectangle { color:"#444" } - Row { - id: tagLine; - anchors.top: description.bottom - anchors.left: parent.left - anchors.topMargin: 5 - anchors.leftMargin: 10 - anchors.rightMargin: 26 - Text { id: labelText; text: "Tags: " ; color: "#999"; font.pixelSize: 11} - Text { text: model.tags.join(", "); color: "#bbb"; font.pixelSize: 11} - } - MouseArea { id: mouseArea anchors.fill: parent @@ -121,7 +112,39 @@ Rectangle { onExited: parent.state = "" } - + Row { + id: tagLine; + anchors.bottomMargin: 20 + anchors.top: description.bottom + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.rightMargin: 26 + spacing: 4 + Text { id: labelText; text: qsTr("Tags:") ; color: "#999"; font.pixelSize: 11} + Repeater { + model: tags; + Text { + states: [ State { name: "hover"; PropertyChanges { target: tagText; color: "black" } } ] + id: tagText + text: model.modelData + color: "#bbb" + font.pixelSize: 11 + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + root.state = "hover" + parent.state = "hover" + } + onExited:{ + root.state = "" + parent.state = "" + } + onClicked: root.tagClicked(model.modelData) + } + } + } + } states: [ State { name: "hover"; PropertyChanges { target: root; color: "#f9f9f9" } } ] } diff --git a/src/plugins/qtsupport/exampleslistmodel.cpp b/src/plugins/qtsupport/exampleslistmodel.cpp index 191b7d4259b1a05c0ed3e489294892894a32d90f..d83a52f10a36bdfc6db53151a66c678b7d5ff974 100644 --- a/src/plugins/qtsupport/exampleslistmodel.cpp +++ b/src/plugins/qtsupport/exampleslistmodel.cpp @@ -374,17 +374,52 @@ void ExamplesListModelFilter::updateFilter() invalidateFilter(); } +bool containsSubString(const QStringList& list, const QString& substr, Qt::CaseSensitivity cs) +{ + foreach (const QString &elem, list) { + if (elem.contains(substr, cs)) + return true; + } + + return false; +} + bool ExamplesListModelFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (m_showTutorialsOnly) { int type = sourceModel()->index(sourceRow, 0, sourceParent).data(Type).toInt(); if (type != Tutorial) return false; - // tag search only active if we are not in tutorial only mode - } else if (!m_filterTag.isEmpty()) { - QStringList tags = sourceModel()->index(sourceRow, 0, sourceParent).data(Tags).toStringList(); - if (!tags.contains(m_filterTag, Qt::CaseInsensitive)) - return false; + } + + const QStringList tags = sourceModel()->index(sourceRow, 0, sourceParent).data(Tags).toStringList(); + + if (!m_filterTags.isEmpty()) { + foreach(const QString &tag, m_filterTags) { + if (!tags.contains(tag, Qt::CaseInsensitive)) + return false; + } + return true; + } + + if (!m_searchString.isEmpty()) { + const QString description = sourceModel()->index(sourceRow, 0, sourceParent).data(Description).toString(); + const QString name = sourceModel()->index(sourceRow, 0, sourceParent).data(Name).toString(); + + + foreach(const QString &subString, m_searchString) { + bool wordMatch = false; + wordMatch |= (bool)name.contains(subString, Qt::CaseInsensitive); + if (wordMatch) + continue; + // TODO: match substring + wordMatch |= containsSubString(tags, subString, Qt::CaseInsensitive); + if (wordMatch) + continue; + wordMatch |= (bool)description.contains(subString, Qt::CaseInsensitive); + if (!wordMatch) + return false; + } } bool ok = QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); @@ -400,5 +435,113 @@ void ExamplesListModelFilter::setShowTutorialsOnly(bool showTutorialsOnly) emit showTutorialsOnlyChanged(); } +struct SearchStringLexer { + QString code; + const QChar *codePtr; + QChar yychar; + QString yytext; + + enum TokenKind { + END_OF_STRING = 0, + TAG, + STRING_LITERAL, + UNKNOWN + }; + + inline void yyinp() { yychar = *codePtr++; } + + SearchStringLexer(const QString &code) + : code(code) + , codePtr(code.unicode()) + , yychar(' ') { } + + int operator()() { return yylex(); } + + int yylex() { + while (yychar.isSpace()) + yyinp(); // skip all the spaces + + yytext.clear(); + + if (yychar.isNull()) + return END_OF_STRING; + + QChar ch = yychar; + yyinp(); + + switch (ch.unicode()) { + case '"': + case '\'': + { + const QChar quote = ch; + yytext.clear(); + while (!yychar.isNull()) { + if (yychar == quote) { + yyinp(); + break; + } if (yychar == '\\') { + yyinp(); + switch (yychar.unicode()) { + case '"': yytext += '"'; yyinp(); break; + case '\'': yytext += '\''; yyinp(); break; + case '\\': yytext += '\\'; yyinp(); break; + } + } else { + yytext += yychar; + yyinp(); + } + } + return STRING_LITERAL; + } + + default: + if (ch.isLetterOrNumber() || ch == '_') { + yytext.clear(); + yytext += ch; + while (yychar.isLetterOrNumber() || yychar == '_') { + yytext += yychar; + yyinp(); + } + if (yychar == ':' && yytext == QLatin1String("tag")) { + yyinp(); + return TAG; + } + return STRING_LITERAL; + } + } + + yytext += ch; + return UNKNOWN; + } +}; + +void ExamplesListModelFilter::parseSearchString(const QString &arg) +{ + QStringList tags; + QStringList searchTerms; + SearchStringLexer lex(arg); + bool isTag = false; + while (int tk = lex()) { + if (tk == SearchStringLexer::TAG) { + isTag = true; + searchTerms.append(lex.yytext); + } + + if (tk == SearchStringLexer::STRING_LITERAL) { + if (isTag) { + searchTerms.pop_back(); + tags.append(lex.yytext); + isTag = false; + } else { + searchTerms.append(lex.yytext); + } + } + } + + setSearchStrings(searchTerms); + setFilterTags(tags); + updateFilter(); +} + } // namespace Internal } // namespace QtSupport diff --git a/src/plugins/qtsupport/exampleslistmodel.h b/src/plugins/qtsupport/exampleslistmodel.h index fc36970f0b0e3253431739ea66c11d97e6de68ff..cdac8c5abc9f8c126e9f9f1e80c277ba110b2dfd 100644 --- a/src/plugins/qtsupport/exampleslistmodel.h +++ b/src/plugins/qtsupport/exampleslistmodel.h @@ -38,6 +38,8 @@ #include <QtCore/QXmlStreamReader> #include <QtGui/QSortFilterProxyModel> +#include <qdebug.h> + namespace QtSupport { namespace Internal { @@ -105,39 +107,52 @@ class ExamplesListModelFilter : public QSortFilterProxyModel { Q_OBJECT public: Q_PROPERTY(bool showTutorialsOnly READ showTutorialsOnly WRITE setShowTutorialsOnly NOTIFY showTutorialsOnlyChanged) - Q_PROPERTY(QString filterTag READ filterTag WRITE setFilterTag NOTIFY filterTagChanged) + Q_PROPERTY(QStringList filterTags READ filterTags WRITE setFilterTags NOTIFY filterTagsChanged) + Q_PROPERTY(QStringList searchStrings READ searchStrings WRITE setSearchStrings NOTIFY searchStrings) explicit ExamplesListModelFilter(QObject *parent); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + bool showTutorialsOnly() {return m_showTutorialsOnly;} + QStringList filterTags() const { return m_filterTags; } + QStringList searchStrings() const { return m_searchString; } - QString filterTag() const +public slots: + void setFilterTags(const QStringList& arg) { - return m_filterTag; + if (m_filterTags != arg) { + m_filterTags = arg; + emit filterTagsChanged(arg); + } } + void updateFilter(); -public slots: - void setFilterTag(const QString& arg) + void setSearchStrings(const QStringList& arg) { - if (m_filterTag != arg) { - m_filterTag = arg; - emit filterTagChanged(arg); + if (m_searchString != arg) { + m_searchString = arg; + emit searchStrings(arg); + updateFilter(); } } - void updateFilter(); + + void parseSearchString(const QString& arg); signals: void showTutorialsOnlyChanged(); - void filterTagChanged(const QString& arg); + void filterTagsChanged(const QStringList& arg); + + void searchStrings(const QStringList& arg); private slots: void setShowTutorialsOnly(bool showTutorialsOnly); private: bool m_showTutorialsOnly; - QString m_filterTag; + QStringList m_filterTags; + QStringList m_searchString; }; } // namespace Internal diff --git a/src/plugins/welcome/welcomeplugin.cpp b/src/plugins/welcome/welcomeplugin.cpp index 85b40f286e21dab2c4418a6a890c1f1220d40f37..618a6731c31c450e16fba10009181636d2fa8f58 100644 --- a/src/plugins/welcome/welcomeplugin.cpp +++ b/src/plugins/welcome/welcomeplugin.cpp @@ -63,7 +63,7 @@ #include <QtDeclarative/QDeclarativeEngine> #include <QtDeclarative/QDeclarativeNetworkAccessManagerFactory> -enum { debug = 0 }; +enum { debug = 1 }; using namespace ExtensionSystem;