Commit 26851ec3 authored by Daniel Molkentin's avatar Daniel Molkentin
Browse files

WelcomeScreen: Implementing proper search lexer/parser for the lineedit.

Now copes with quotes and "tag:" prefixes.

Change-Id: I348e533ab290242fd05a034a2912cc95f0a9a3da
Done-with: Roberto Raggi
Reviewed-on: http://codereview.qt.nokia.com/2038

Reviewed-by: default avatarDaniel Molkentin <daniel.molkentin@nokia.com>
parent e6dbfb2d
......@@ -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)
......
......@@ -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" } } ]
}
......@@ -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
......@@ -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
......
......@@ -63,7 +63,7 @@
#include <QtDeclarative/QDeclarativeEngine>
#include <QtDeclarative/QDeclarativeNetworkAccessManagerFactory>
enum { debug = 0 };
enum { debug = 1 };
using namespace ExtensionSystem;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment