mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-04-15 05:32:21 +00:00
Search/Filter using Lucene
Searching and filtering the ride list using a search box. This is implemented using a new optional dependency on CLucene. Fixes #627.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
John Ehrlinger
|
||||
|
||||
May 2011
|
||||
Version 1.0
|
||||
Version 1.1
|
||||
|
||||
A walkthrough of building GoldenCheetah from scratch on Ubuntu linux. This walkthrough
|
||||
should be largely the same for any Linux distro.
|
||||
@@ -31,7 +31,8 @@ CONTENTS
|
||||
- flex
|
||||
- bison
|
||||
- libical - Diary window and CalDAV support (google/mobileme calendar integration)
|
||||
- libvlc - Video playback in training mode
|
||||
- libvlc - Video playback in training mode
|
||||
- clucene - Indexing/Searching ride files
|
||||
|
||||
|
||||
1. BASIC INSTALLATION WITH MANDATORY DEPENDENCIES
|
||||
@@ -398,3 +399,29 @@ VLC_INSTALL = /usr/include/vlc/
|
||||
$ make clean
|
||||
$ qmake
|
||||
$ make
|
||||
|
||||
CLUCENE - Indexing and Searching ride files (search box)
|
||||
--------------------------------------------------------
|
||||
|
||||
You will need clucene runtime and core libraries, we developed against 0.9.21b-2 but
|
||||
any 0.9 branch should work fine, let us know if you experience any issues. You may find
|
||||
that the libclucene0ldbl runtime is already installed, this is fine and typical since
|
||||
clucene is a very popular search library.
|
||||
|
||||
$ sudo apt-get install clucene-core
|
||||
$ sudo apt-get install libclucene0ldbl
|
||||
|
||||
By default, and this is deliberate, the clucene install places the config headers into
|
||||
a platform specific location. For my install I just copy the platform (linux) specific
|
||||
header config into the normal /usr/include/CLucene directory with the following:
|
||||
|
||||
$ sudo cp /usr/lib/CLucene/clucene-config.h /usr/include/CLucene
|
||||
|
||||
Next we need to comment out the two CLUCENE lines in gcconfig.pri and they should read:
|
||||
|
||||
CLUCENE_INCLUDE = /usr/include/CLucene
|
||||
CLUCENE_LIBS = -lclucene
|
||||
|
||||
$ make clean
|
||||
$ qmake
|
||||
$ make
|
||||
|
||||
@@ -59,8 +59,9 @@
|
||||
// 37 06 Apr 2012 Rainer Clasen Added non-zero average Power (watts)
|
||||
// 38 8th Jul 2012 Mark Liversedge Computes metrics for manual files now
|
||||
// 39 18 Aug 2012 Mark Liversedge New metric LRBalance
|
||||
// 40 20 Oct 2012 Mark Liversedge Lucene search/filter and checkbox metadata field
|
||||
|
||||
static int DBSchemaVersion = 39;
|
||||
static int DBSchemaVersion = 40;
|
||||
|
||||
DBAccess::DBAccess(MainWindow* main, QDir home) : main(main), home(home)
|
||||
{
|
||||
@@ -178,7 +179,7 @@ bool DBAccess::createMetricsTable()
|
||||
|
||||
// And all the metadata texts
|
||||
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 3)) {
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 3 || field.type == 7)) {
|
||||
createMetricTable += QString(", Z%1 varchar").arg(main->specialFields.makeTechName(field.name));
|
||||
}
|
||||
}
|
||||
@@ -254,7 +255,7 @@ bool DBAccess::createMeasuresTable()
|
||||
|
||||
// And all the metadata texts
|
||||
foreach(FieldDefinition field, fieldDefinitions)
|
||||
if (field.type < 3) createMeasuresTable += QString(", Z%1 varchar").arg(main->specialFields.makeTechName(field.name));
|
||||
if (field.type < 3 || field.type == 7) createMeasuresTable += QString(", Z%1 varchar").arg(main->specialFields.makeTechName(field.name));
|
||||
|
||||
// And all the metadata measures
|
||||
foreach(FieldDefinition field, fieldDefinitions)
|
||||
@@ -401,7 +402,7 @@ bool DBAccess::importRide(SummaryMetrics *summaryMetrics, RideFile *ride, QColor
|
||||
|
||||
// And all the metadata texts
|
||||
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 3)) {
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 3 || field.type == 7)) {
|
||||
insertStatement += QString(", Z%1 ").arg(main->specialFields.makeTechName(field.name));
|
||||
}
|
||||
}
|
||||
@@ -416,7 +417,7 @@ bool DBAccess::importRide(SummaryMetrics *summaryMetrics, RideFile *ride, QColor
|
||||
for (int i=0; i<factory.metricCount(); i++)
|
||||
insertStatement += ",?";
|
||||
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
|
||||
if (!main->specialFields.isMetric(field.name) && field.type < 5) {
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
|
||||
insertStatement += ",?";
|
||||
}
|
||||
}
|
||||
@@ -440,7 +441,7 @@ bool DBAccess::importRide(SummaryMetrics *summaryMetrics, RideFile *ride, QColor
|
||||
// And all the metadata texts
|
||||
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
|
||||
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 3)) {
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 3 || field.type ==7)) {
|
||||
query.addBindValue(ride->getTag(field.name, ""));
|
||||
}
|
||||
}
|
||||
@@ -499,7 +500,7 @@ DBAccess::getRide(QString filename, SummaryMetrics &summaryMetrics, QColor&color
|
||||
for (int i=0; i<factory.metricCount(); i++)
|
||||
selectStatement += QString(", X%1 ").arg(factory.metricName(i));
|
||||
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
|
||||
if (!main->specialFields.isMetric(field.name) && field.type < 5) {
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
|
||||
selectStatement += QString(", Z%1 ").arg(main->specialFields.makeTechName(field.name));
|
||||
}
|
||||
}
|
||||
@@ -556,7 +557,7 @@ QList<SummaryMetrics> DBAccess::getAllMetricsFor(QDateTime start, QDateTime end)
|
||||
for (int i=0; i<factory.metricCount(); i++)
|
||||
selectStatement += QString(", X%1 ").arg(factory.metricName(i));
|
||||
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
|
||||
if (!main->specialFields.isMetric(field.name) && field.type < 5) {
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
|
||||
selectStatement += QString(", Z%1 ").arg(main->specialFields.makeTechName(field.name));
|
||||
}
|
||||
}
|
||||
@@ -585,7 +586,7 @@ QList<SummaryMetrics> DBAccess::getAllMetricsFor(QDateTime start, QDateTime end)
|
||||
QString underscored = field.name;
|
||||
summaryMetrics.setForSymbol(underscored.replace("_"," "), query.value(i+3).toDouble());
|
||||
i++;
|
||||
} else if (!main->specialFields.isMetric(field.name) && field.type < 3) {
|
||||
} else if (!main->specialFields.isMetric(field.name) && (field.type < 3 || field.type == 7)) {
|
||||
QString underscored = field.name;
|
||||
// ignore texts for now XXX todo if want metadata from Summary Metrics
|
||||
summaryMetrics.setText(underscored.replace("_"," "), query.value(i+3).toString());
|
||||
@@ -607,7 +608,7 @@ SummaryMetrics DBAccess::getRideMetrics(QString filename)
|
||||
for (int i=0; i<factory.metricCount(); i++)
|
||||
selectStatement += QString(", X%1 ").arg(factory.metricName(i));
|
||||
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
|
||||
if (!main->specialFields.isMetric(field.name) && field.type < 5) {
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
|
||||
selectStatement += QString(", Z%1 ").arg(main->specialFields.makeTechName(field.name));
|
||||
}
|
||||
}
|
||||
@@ -631,7 +632,7 @@ SummaryMetrics DBAccess::getRideMetrics(QString filename)
|
||||
QString underscored = field.name;
|
||||
summaryMetrics.setForSymbol(underscored.replace(" ","_"), query.value(i+2).toDouble());
|
||||
i++;
|
||||
} else if (!main->specialFields.isMetric(field.name) && field.type < 3) {
|
||||
} else if (!main->specialFields.isMetric(field.name) && (field.type < 3 || field.type == 7)) {
|
||||
// ignore texts for now XXX todo if want metadata from Summary Metrics
|
||||
QString underscored = field.name;
|
||||
summaryMetrics.setText(underscored.replace("_"," "), query.value(i+2).toString());
|
||||
@@ -654,7 +655,7 @@ bool DBAccess::importMeasure(SummaryMetrics *summaryMetrics)
|
||||
|
||||
// And all the metadata texts
|
||||
foreach(FieldDefinition field, mfieldDefinitions) {
|
||||
if (field.type < 3) {
|
||||
if (field.type < 3 || field.type == 7) {
|
||||
insertStatement += QString(", Z%1 ").arg(msp.makeTechName(field.name));
|
||||
}
|
||||
}
|
||||
@@ -668,7 +669,7 @@ bool DBAccess::importMeasure(SummaryMetrics *summaryMetrics)
|
||||
insertStatement += " ) values (?,?"; // timestamp, measure_date
|
||||
|
||||
foreach(FieldDefinition field, mfieldDefinitions) {
|
||||
if (field.type < 5) {
|
||||
if (field.type < 5 || field.type == 7) {
|
||||
insertStatement += ",?";
|
||||
}
|
||||
}
|
||||
@@ -682,7 +683,7 @@ bool DBAccess::importMeasure(SummaryMetrics *summaryMetrics)
|
||||
|
||||
// And all the text measures
|
||||
foreach(FieldDefinition field, mfieldDefinitions) {
|
||||
if (field.type < 3) {
|
||||
if (field.type < 3 || field.type == 7) {
|
||||
query.addBindValue(summaryMetrics->getText(field.name, ""));
|
||||
}
|
||||
}
|
||||
@@ -720,7 +721,7 @@ QList<SummaryMetrics> DBAccess::getAllMeasuresFor(QDateTime start, QDateTime end
|
||||
// construct the select statement
|
||||
QString selectStatement = "SELECT timestamp, measure_date";
|
||||
foreach(FieldDefinition field, fieldDefinitions) {
|
||||
if (!main->specialFields.isMetric(field.name) && field.type < 5) {
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
|
||||
selectStatement += QString(", Z%1 ").arg(main->specialFields.makeTechName(field.name));
|
||||
}
|
||||
}
|
||||
@@ -744,7 +745,7 @@ QList<SummaryMetrics> DBAccess::getAllMeasuresFor(QDateTime start, QDateTime end
|
||||
if (field.type == 3 || field.type == 4) {
|
||||
add.setText(field.name, query.value(i).toString());
|
||||
i++;
|
||||
} else if (field.type < 3) {
|
||||
} else if (field.type < 3 || field.type == 7) {
|
||||
add.setText(field.name, query.value(i).toString());
|
||||
i++;
|
||||
}
|
||||
|
||||
156
src/Lucene.cpp
Normal file
156
src/Lucene.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Mark Liversedge (liversedge@gmail.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "Lucene.h"
|
||||
#include "MainWindow.h"
|
||||
|
||||
// stdc strings
|
||||
using namespace std;
|
||||
// here we go
|
||||
using namespace lucene::analysis;
|
||||
using namespace lucene::index;
|
||||
using namespace lucene::document;
|
||||
using namespace lucene::queryParser;
|
||||
using namespace lucene::search;
|
||||
using namespace lucene::store;
|
||||
|
||||
Lucene::Lucene(MainWindow *parent) : QObject(parent), main(parent)
|
||||
{
|
||||
// create the directory if needed
|
||||
main->home.mkdir("index");
|
||||
|
||||
// make index directory if needed
|
||||
QDir dir(main->home.canonicalPath() + "/index");
|
||||
|
||||
try {
|
||||
|
||||
bool indexExists = IndexReader::indexExists(dir.canonicalPath().toLocal8Bit().data());
|
||||
|
||||
// clear any locks
|
||||
if (indexExists && IndexReader::isLocked(dir.canonicalPath().toLocal8Bit().data()))
|
||||
IndexReader::unlock(dir.canonicalPath().toLocal8Bit().data());
|
||||
|
||||
if (!indexExists) {
|
||||
|
||||
IndexWriter *create = new IndexWriter(dir.canonicalPath().toLocal8Bit().data(), &analyzer, true);
|
||||
|
||||
// lets flush to disk and reopen
|
||||
create->close();
|
||||
delete create;
|
||||
}
|
||||
|
||||
// now lets open using a mnodifier since the API is much simpler
|
||||
writer = new IndexModifier(dir.canonicalPath().toLocal8Bit().data(), &analyzer, false); // for updates
|
||||
|
||||
} catch (CLuceneError &e) {
|
||||
|
||||
qDebug()<<"clucene error!"<<e.what();
|
||||
}
|
||||
}
|
||||
|
||||
Lucene::~Lucene()
|
||||
{
|
||||
writer->flush();
|
||||
writer->close();
|
||||
//XXXdelete writer; Causes a SEGV !?
|
||||
}
|
||||
|
||||
bool Lucene::importRide(SummaryMetrics *, RideFile *ride, QColor , unsigned long, bool)
|
||||
{
|
||||
// create a document
|
||||
Document doc;
|
||||
|
||||
// add Filename special field (unique)
|
||||
std::wstring cname = ride->getTag("Filename","").toStdWString();
|
||||
doc.add( *_CLNEW Field(_T("Filename"), cname.c_str(), Field::STORE_YES | Field::INDEX_UNTOKENIZED));
|
||||
|
||||
// And all the metadata texts
|
||||
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
|
||||
|
||||
if (!main->specialFields.isMetric(field.name) && (field.type < 3 || field.type == 7)) {
|
||||
|
||||
std::wstring name = main->specialFields.makeTechName(field.name).toStdWString();
|
||||
std::wstring value = ride->getTag(field.name,"").toStdWString();
|
||||
|
||||
doc.add( *_CLNEW Field(name.c_str(), value.c_str(), Field::STORE_YES | Field::INDEX_TOKENIZED));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// delete if already in the index
|
||||
deleteRide(ride->getTag("Filename", ""));
|
||||
|
||||
// now add to index
|
||||
writer->addDocument(&doc);
|
||||
doc.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lucene::deleteRide(QString name)
|
||||
{
|
||||
std::wstring cname = name.toStdWString();
|
||||
Term term(_T("Filename"), cname.c_str());
|
||||
|
||||
return (writer->deleteDocuments(&term)>0);
|
||||
}
|
||||
|
||||
void Lucene::optimise()
|
||||
{
|
||||
writer->flush();
|
||||
writer->optimize();
|
||||
}
|
||||
|
||||
int Lucene::search(QString query)
|
||||
{
|
||||
|
||||
try {
|
||||
// parse query
|
||||
QueryParser parser(_T("Notes"), &analyzer);
|
||||
parser.setPhraseSlop(4);
|
||||
|
||||
std::wstring querystring = query.toStdWString();
|
||||
Query* lquery = parser.parse(querystring.c_str());
|
||||
|
||||
if (lquery == NULL) return 0;
|
||||
|
||||
reader = IndexReader::open(writer->getDirectory()); // for querying against
|
||||
searcher = new IndexSearcher(reader); // to perform searches
|
||||
|
||||
// go find hits
|
||||
hits = searcher->search(lquery);
|
||||
filenames.clear();
|
||||
|
||||
for (int i=0; i< hits->length(); i++) {
|
||||
Document *d = &hits->doc(i);
|
||||
filenames << QString::fromWCharArray(d->get(_T("Filename")));
|
||||
}
|
||||
|
||||
delete hits;
|
||||
delete lquery;
|
||||
delete searcher;
|
||||
delete reader;
|
||||
|
||||
} catch (CLuceneError &e) {
|
||||
|
||||
qDebug()<<"clucene error:"<<e.what();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return filenames.count();
|
||||
}
|
||||
78
src/Lucene.h
Normal file
78
src/Lucene.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Mark Liversedge (liversedge@gmail.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _GC_Lucene_h
|
||||
#define _GC_Lucene_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "RideMetadata.h"
|
||||
#include "SummaryMetrics.h"
|
||||
#include "RideFile.h"
|
||||
|
||||
#include "CLucene.h"
|
||||
#include "CLucene/index/IndexModifier.h"
|
||||
|
||||
using namespace lucene::analysis;
|
||||
using namespace lucene::index;
|
||||
using namespace lucene::document;
|
||||
using namespace lucene::queryParser;
|
||||
using namespace lucene::search;
|
||||
using namespace lucene::store;
|
||||
|
||||
class Lucene : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Lucene(MainWindow *parent = 0);
|
||||
~Lucene();
|
||||
|
||||
// Create/Delete Metrics
|
||||
bool importRide(SummaryMetrics *summaryMetrics, RideFile *ride, QColor color, unsigned long, bool);
|
||||
bool deleteRide(QString);
|
||||
void optimise(); // for optimising the index once updated
|
||||
|
||||
// search
|
||||
int search(QString query); // run query and return number of results found
|
||||
QStringList &files() { return filenames; }
|
||||
|
||||
protected:
|
||||
|
||||
private slots:
|
||||
|
||||
signals:
|
||||
|
||||
private:
|
||||
MainWindow *main;
|
||||
|
||||
// CLucene objects
|
||||
SimpleAnalyzer analyzer;
|
||||
IndexModifier* writer;
|
||||
IndexReader* reader;
|
||||
IndexSearcher* searcher;
|
||||
|
||||
// Query results
|
||||
Hits *hits; // null when no results
|
||||
QStringList filenames;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -89,6 +89,11 @@
|
||||
#include "QTFullScreen.h"
|
||||
#endif
|
||||
|
||||
#ifdef GC_HAVE_LUCENE
|
||||
#include "SearchBox.h"
|
||||
#include "Lucene.h"
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <QApplication>
|
||||
#include <QtGui>
|
||||
@@ -141,7 +146,7 @@ MainWindow::MainWindow(const QDir &home) :
|
||||
GCColor *GCColorSet = new GCColor(this); // get/keep colorset
|
||||
GCColorSet->colorSet(); // shut up the compiler
|
||||
setStyleSheet("QFrame { FrameStyle = QFrame::NoFrame };"
|
||||
"QWidget { background = Qt::white; border:0 px; margin: 2px; };"
|
||||
"QWidget { background = Qt::white; border:0 px; margin: 0px; };"
|
||||
"QTabWidget { background = Qt::white; };"
|
||||
"::pane { FrameStyle = QFrame::NoFrame; border: 0px; };");
|
||||
|
||||
@@ -196,6 +201,9 @@ MainWindow::MainWindow(const QDir &home) :
|
||||
// Metadata fields
|
||||
_rideMetadata = new RideMetadata(this,true);
|
||||
_rideMetadata->hide();
|
||||
#ifdef GC_HAVE_LUCENE
|
||||
lucene = new Lucene(this); // before metricDB attempts to refresh
|
||||
#endif
|
||||
metricDB = new MetricAggregator(this, home, zones(), hrZones()); // just to catch config updates!
|
||||
metricDB->refreshMetrics();
|
||||
|
||||
@@ -321,7 +329,7 @@ MainWindow::MainWindow(const QDir &home) :
|
||||
analButtons->setContentsMargins(0,0,0,0);
|
||||
analButtons->setFocusPolicy(Qt::NoFocus);
|
||||
analButtons->setAutoFillBackground(false);
|
||||
analButtons->setStyleSheet("background-color: rgba( 255, 255, 255, 0% ); border: 0px;");
|
||||
//XXX analButtons->setStyleSheet("background-color: rgba( 255, 255, 255, 0% ); border: 0px;");
|
||||
analButtons->setLayout(toolbuttons);
|
||||
analButtons->show();
|
||||
|
||||
@@ -449,6 +457,15 @@ MainWindow::MainWindow(const QDir &home) :
|
||||
listView->setColumns(appsettings->cvalue(cyclist, GC_NAVHEADINGS).toString());
|
||||
listView->setWidths(appsettings->cvalue(cyclist, GC_NAVHEADINGWIDTHS).toString());
|
||||
}
|
||||
#ifdef GC_HAVE_LUCENE
|
||||
searchBox = new SearchBox(this);
|
||||
toolbuttons->addWidget(searchBox);
|
||||
//toolbuttons->addStretch();
|
||||
connect(searchBox, SIGNAL(submitQuery(QString)), this, SLOT(searchSubmitted(QString)));
|
||||
connect(searchBox, SIGNAL(clearQuery()), this, SLOT(searchCleared()));
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// INTERVALS
|
||||
intervalSummaryWindow = new IntervalSummaryWindow(this);
|
||||
@@ -560,7 +577,16 @@ MainWindow::MainWindow(const QDir &home) :
|
||||
currentWindow = analWindow;
|
||||
|
||||
// POPULATE TOOLBOX
|
||||
toolBox->addItem(listView, QIcon(":images/activity.png"), "Activity History");
|
||||
QWidget *activityHistory = new QWidget(this);
|
||||
activityHistory->setContentsMargins(0,0,0,0);
|
||||
activityHistory->setStyleSheet("padding: 0px; border: 0px; margin: 0px;");
|
||||
QVBoxLayout *activityLayout = new QVBoxLayout(activityHistory);
|
||||
activityLayout->setSpacing(0);
|
||||
activityLayout->setContentsMargins(0,0,0,0);
|
||||
activityLayout->addWidget(searchBox);
|
||||
activityLayout->addWidget(listView);
|
||||
|
||||
toolBox->addItem(activityHistory, QIcon(":images/activity.png"), "Activity History");
|
||||
toolBox->addItem(intervalSplitter, QIcon(":images/stopwatch.png"), "Activity Intervals");
|
||||
toolBox->addItem(trainTool->controls(), QIcon(":images/library.png"), "Workout Library");
|
||||
toolBox->addItem(masterControls, QIcon(":images/settings.png"), "Chart Settings");
|
||||
@@ -2224,3 +2250,16 @@ MainWindow::setBubble(QString text, QPoint pos, Qt::Orientation orientation)
|
||||
bubble->repaint();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef GC_HAVE_LUCENE
|
||||
void MainWindow::searchSubmitted(QString query)
|
||||
{
|
||||
int count = lucene->search(query);
|
||||
emit searchResults(lucene->files());
|
||||
}
|
||||
|
||||
void MainWindow::searchCleared()
|
||||
{
|
||||
emit searchClear();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -62,6 +62,8 @@ class GcBubble;
|
||||
class LionFullScreen;
|
||||
class QTFullScreen;
|
||||
class TrainTool;
|
||||
class Lucene;
|
||||
class SearchBox;
|
||||
|
||||
extern QList<MainWindow *> mainwindows; // keep track of all the MainWindows we have open
|
||||
|
||||
@@ -151,6 +153,10 @@ class MainWindow : public QMainWindow
|
||||
QTFullScreen *fullScreen;
|
||||
#endif
|
||||
TrainTool *trainTool;
|
||||
#ifdef GC_HAVE_LUCENE
|
||||
SearchBox *searchBox;
|
||||
Lucene *lucene;
|
||||
#endif
|
||||
|
||||
// *********************************************
|
||||
// APPLICATION EVENTS
|
||||
@@ -207,6 +213,13 @@ class MainWindow : public QMainWindow
|
||||
void rideDirty();
|
||||
void rideClean();
|
||||
|
||||
// search
|
||||
#ifdef GC_HAVE_LUCENE
|
||||
void searchSubmit(QString);
|
||||
void searchResults(QStringList);
|
||||
void searchClear();
|
||||
#endif
|
||||
|
||||
// realtime
|
||||
void telemetryUpdate(RealtimeData rtData);
|
||||
void ergFileSelected(ErgFile *);
|
||||
@@ -315,6 +328,11 @@ class MainWindow : public QMainWindow
|
||||
void toggleFullScreen();
|
||||
#endif
|
||||
|
||||
#ifdef GC_HAVE_LUCENE
|
||||
void searchSubmitted(QString);
|
||||
void searchCleared();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
||||
static QString notesFileName(QString rideFileName);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "DBAccess.h"
|
||||
#include "RideFile.h"
|
||||
#include "RideFileCache.h"
|
||||
#include "Lucene.h"
|
||||
#include "Zones.h"
|
||||
#include "HrZones.h"
|
||||
#include "Settings.h"
|
||||
@@ -90,6 +91,7 @@ void MetricAggregator::refreshMetrics()
|
||||
for (d = dbStatus.begin(); d != dbStatus.end(); ++d) {
|
||||
if (QFile(home.absolutePath() + "/" + d.key()).exists() == false) {
|
||||
dbaccess->deleteRide(d.key());
|
||||
main->lucene->deleteRide(d.key());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +179,7 @@ void MetricAggregator::refreshMetrics()
|
||||
|
||||
// end LUW -- now syncs DB
|
||||
dbaccess->connection().commit();
|
||||
main->lucene->optimise();
|
||||
|
||||
main->isclean = true;
|
||||
|
||||
@@ -234,6 +237,7 @@ bool MetricAggregator::importRide(QDir path, RideFile *ride, QString fileName, u
|
||||
QColor color = colorEngine->colorFor(ride->getTag("Calendar Text", ""));
|
||||
|
||||
dbaccess->importRide(summaryMetric, ride, color, fingerprint, modify);
|
||||
main->lucene->importRide(summaryMetric, ride, color, fingerprint, modify);
|
||||
delete summaryMetric;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1701,6 +1701,7 @@ static void addFieldTypes(QComboBox *p)
|
||||
p->addItem("Double");
|
||||
p->addItem("Date");
|
||||
p->addItem("Time");
|
||||
p->addItem("Checkbox");
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -316,7 +316,7 @@ RideFile *RideFileFactory::openRideFile(MainWindow *main, QFile &file,
|
||||
result->setTag("Calendar Text", calendarText);
|
||||
|
||||
// set other "special" fields
|
||||
result->setTag("Filename", file.fileName());
|
||||
result->setTag("Filename", QFileInfo(file.fileName()).fileName());
|
||||
result->setTag("Device", result->deviceType());
|
||||
result->setTag("Athlete", QFileInfo(file).dir().dirName());
|
||||
result->setTag("Year", result->startTime().toString("yyyy"));
|
||||
|
||||
@@ -480,6 +480,12 @@ FormField::FormField(FieldDefinition field, RideMetadata *meta) : definition(fie
|
||||
connect (widget, SIGNAL(timeChanged(const QTime)), this, SLOT(dataChanged()));
|
||||
connect (widget, SIGNAL(editingFinished()), this, SLOT(editFinished()));
|
||||
break;
|
||||
|
||||
case FIELD_CHECKBOX : // check
|
||||
widget = new QCheckBox(this);
|
||||
//widget->setFixedHeight(18);
|
||||
connect(widget, SIGNAL(stateChanged(int)), this, SLOT(stateChanged(int)));
|
||||
break;
|
||||
}
|
||||
//widget->setFont(font);
|
||||
//connect(main, SIGNAL(rideSelected()), this, SLOT(rideSelected()));
|
||||
@@ -504,6 +510,7 @@ FormField::~FormField()
|
||||
case FIELD_DOUBLE : delete ((QDoubleSpinBox*)widget); break;
|
||||
case FIELD_DATE : delete ((QDateEdit*)widget); break;
|
||||
case FIELD_TIME : delete ((QTimeEdit*)widget); break;
|
||||
case FIELD_CHECKBOX : delete ((QCheckBox*)widget); break;
|
||||
}
|
||||
if (enabled) delete enabled;
|
||||
}
|
||||
@@ -628,7 +635,14 @@ FormField::editFinished()
|
||||
void
|
||||
FormField::stateChanged(int state)
|
||||
{
|
||||
if (active) return; // being updated programmatically
|
||||
if (active || ourRideItem == NULL) return; // being updated programmatically
|
||||
|
||||
// are we a checkbox -- do the simple stuff
|
||||
if (definition.type == FIELD_CHECKBOX) {
|
||||
ourRideItem->ride()->setTag(definition.name, ((QCheckBox *)widget)->isChecked() ? "1" : "0");
|
||||
ourRideItem->setDirty(true);
|
||||
return;
|
||||
}
|
||||
|
||||
widget->setEnabled(state ? true : false);
|
||||
widget->setHidden(state ? false : true);
|
||||
@@ -752,6 +766,12 @@ FormField::metadataChanged()
|
||||
((QTimeEdit*)widget)->setTime(time);
|
||||
}
|
||||
break;
|
||||
|
||||
case FIELD_CHECKBOX : // checkbox
|
||||
{
|
||||
((QCheckBox*)widget)->setChecked((value == "1") ? true : false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
active = false;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#define FIELD_DOUBLE 4
|
||||
#define FIELD_DATE 5
|
||||
#define FIELD_TIME 6
|
||||
#define FIELD_CHECKBOX 7
|
||||
|
||||
class KeywordDefinition
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ RideNavigator::RideNavigator(MainWindow *parent) : main(parent), active(false),
|
||||
|
||||
mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setSpacing(0);
|
||||
mainLayout->setContentsMargins(3,3,3,3);
|
||||
mainLayout->setContentsMargins(0,0,0,0);
|
||||
|
||||
sqlModel = new QSqlTableModel(this, main->metricDB->db()->connection());
|
||||
sqlModel->setTable("metrics");
|
||||
@@ -52,8 +52,11 @@ RideNavigator::RideNavigator(MainWindow *parent) : main(parent), active(false),
|
||||
sqlModel->select();
|
||||
while (sqlModel->canFetchMore(QModelIndex())) sqlModel->fetchMore(QModelIndex());
|
||||
|
||||
searchFilter = new SearchFilter(this);
|
||||
searchFilter->setSourceModel(sqlModel); // filter out/in search results
|
||||
|
||||
groupByModel = new GroupByModel(this);
|
||||
groupByModel->setSourceModel(sqlModel);
|
||||
groupByModel->setSourceModel(searchFilter);
|
||||
|
||||
sortModel = new BUGFIXQSortFilterProxyModel(this);
|
||||
sortModel->setSourceModel(groupByModel);
|
||||
@@ -118,6 +121,8 @@ RideNavigator::RideNavigator(MainWindow *parent) : main(parent), active(false),
|
||||
connect(tableView,SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(showTreeContextMenuPopup(const QPoint &)));
|
||||
connect(tableView->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(setSortBy(int,Qt::SortOrder)));
|
||||
|
||||
connect(main, SIGNAL(searchResults(QStringList)), this, SLOT(searchStrings(QStringList)));
|
||||
connect(main, SIGNAL(searchClear()), this, SLOT(clearSearch()));
|
||||
// we accept drag and drop operations
|
||||
setAcceptDrops(true);
|
||||
}
|
||||
@@ -177,7 +182,7 @@ RideNavigator::resetView()
|
||||
// add metadata fields...
|
||||
SpecialFields sp; // all the special fields are in here...
|
||||
foreach(FieldDefinition field, main->rideMetadata()->getFields()) {
|
||||
if (!sp.isMetric(field.name) && field.type < 5) {
|
||||
if (!sp.isMetric(field.name) && (field.type < 5 || field.type == 7)) {
|
||||
nameMap.insert(QString("Z%1").arg(sp.makeTechName(field.name)), field.name);
|
||||
}
|
||||
}
|
||||
@@ -239,8 +244,17 @@ RideNavigator::resetView()
|
||||
rideTreeSelectionChanged();
|
||||
}
|
||||
|
||||
void
|
||||
RideNavigator::setWidth(int x)
|
||||
void RideNavigator::searchStrings(QStringList list)
|
||||
{
|
||||
searchFilter->setStrings(list);
|
||||
}
|
||||
|
||||
void RideNavigator::clearSearch()
|
||||
{
|
||||
searchFilter->clearStrings();
|
||||
}
|
||||
|
||||
void RideNavigator::setWidth(int x)
|
||||
{
|
||||
if (init == false) return;
|
||||
|
||||
@@ -248,7 +262,7 @@ RideNavigator::setWidth(int x)
|
||||
|
||||
if (tableView->verticalScrollBar()->isVisible())
|
||||
x -= tableView->verticalScrollBar()->width()
|
||||
+ 6 ; // !! account for content margins of 3,3,3,3
|
||||
+ 0 ; // !! no longer account for content margins of 3,3,3,3 was + 6
|
||||
|
||||
// ** NOTE **
|
||||
// When iterating over the section headings we
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
class NavigatorCellDelegate;
|
||||
class GroupByModel;
|
||||
class SearchFilter;
|
||||
class DiaryWindow;
|
||||
class BUGFIXQSortFilterProxyModel;
|
||||
|
||||
@@ -119,6 +120,9 @@ class RideNavigator : public GcWindow
|
||||
|
||||
void resetView(); // when columns/width changes
|
||||
|
||||
void searchStrings(QStringList);
|
||||
void clearSearch();
|
||||
|
||||
protected:
|
||||
QSqlTableModel *sqlModel; // the sql table
|
||||
GroupByModel *groupByModel; // for group by
|
||||
@@ -147,6 +151,9 @@ class RideNavigator : public GcWindow
|
||||
int _groupBy;
|
||||
QString _columns;
|
||||
QString _widths;
|
||||
|
||||
// search filter
|
||||
SearchFilter *searchFilter;
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
@@ -285,7 +285,7 @@ public:
|
||||
.arg(groupToSourceRow.value(groups[proxyIndex.row()])->count());
|
||||
returning = QVariant(returnString);
|
||||
} else {
|
||||
QString returnString = QString("All %1 activities")
|
||||
QString returnString = QString("%1 activities")
|
||||
.arg(groupToSourceRow.value(groups[proxyIndex.row()])->count());
|
||||
returning = QVariant(returnString);
|
||||
}
|
||||
@@ -494,4 +494,61 @@ class BUGFIXQSortFilterProxyModel : public QSortFilterProxyModel
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class SearchFilter : public QSortFilterProxyModel
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
SearchFilter(QWidget *p) : QSortFilterProxyModel(p), searchActive(false) {}
|
||||
|
||||
void setSourceModel(QAbstractItemModel *model) {
|
||||
QAbstractProxyModel::setSourceModel(model);
|
||||
this->model = model;
|
||||
|
||||
// find the filename column
|
||||
fileIndex = -1;
|
||||
for(int i=0; i<model->columnCount(); i++) {
|
||||
if (model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString() == "filename") {
|
||||
fileIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool filterAcceptsRow (int source_row, const QModelIndex &source_parent) const {
|
||||
|
||||
if (fileIndex == -1 || searchActive == false) return true; // nothing to do
|
||||
|
||||
// lets get the filename
|
||||
QModelIndex source_index = model->index(source_row, fileIndex, source_parent);
|
||||
if (!source_index.isValid()) return true;
|
||||
|
||||
QString key = model->data(source_index, Qt::DisplayRole).toString();
|
||||
return strings.contains(key);
|
||||
}
|
||||
|
||||
public slots:
|
||||
|
||||
void setStrings(QStringList list) {
|
||||
beginResetModel();
|
||||
strings = list;
|
||||
searchActive = true;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void clearStrings() {
|
||||
beginResetModel();
|
||||
strings.clear();
|
||||
searchActive = false;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
private:
|
||||
QAbstractItemModel *model;
|
||||
QStringList strings;
|
||||
int fileIndex;
|
||||
bool searchActive;
|
||||
};
|
||||
#endif
|
||||
|
||||
126
src/SearchBox.cpp
Normal file
126
src/SearchBox.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Mark Liversedge (liversedge@gmail.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "SearchBox.h"
|
||||
#include <QToolButton>
|
||||
|
||||
#include <QStyle>
|
||||
#include <QDebug>
|
||||
|
||||
SearchBox::SearchBox(QWidget *parent)
|
||||
: QLineEdit(parent)
|
||||
{
|
||||
|
||||
//clear button
|
||||
clearButton = new QToolButton(this);
|
||||
QPixmap pixmap(":images/toolbar/clear.png");
|
||||
clearButton->setIcon(QIcon(pixmap));
|
||||
clearButton->setIconSize(pixmap.size());
|
||||
clearButton->setCursor(Qt::ArrowCursor);
|
||||
clearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }");
|
||||
clearButton->hide();
|
||||
connect(clearButton, SIGNAL(clicked()), this, SLOT(clear()));
|
||||
connect(clearButton, SIGNAL(clicked()), this, SIGNAL(clearQuery()));
|
||||
connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateCloseButton(const QString&)));
|
||||
|
||||
// search button
|
||||
searchButton = new QToolButton(this);
|
||||
QPixmap search(":images/toolbar/search.png");
|
||||
searchButton->setIcon(QIcon(search));
|
||||
searchButton->setIconSize(search.size());
|
||||
searchButton->setCursor(Qt::ArrowCursor);
|
||||
searchButton->setStyleSheet("QToolButton { border: none; padding: 0px; }");
|
||||
connect(searchButton, SIGNAL(clicked()), this, SLOT(searchSubmit()));
|
||||
|
||||
int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
|
||||
setStyleSheet(QString( //"QLineEdit { padding-right: %1px; } "
|
||||
"QLineEdit {"
|
||||
" selection-color: white; "
|
||||
//" border: 0px groove gray;"
|
||||
" border-radius: 5px;"
|
||||
" padding: 0px %1px;"
|
||||
"}"
|
||||
"QLineEdit:focus {"
|
||||
" selection-color: white; "
|
||||
//" border: 0px groove gray;"
|
||||
" border-radius: 5px;"
|
||||
" padding: 0px %1px;"
|
||||
"}"
|
||||
""
|
||||
"QLineEdit:edit-focus {"
|
||||
" selection-color: white; "
|
||||
//" border: 0px groove gray;"
|
||||
" border-radius: 5px;"
|
||||
" padding: 0px %1px;"
|
||||
"}"
|
||||
).arg(clearButton->sizeHint().width() + frameWidth + 1));
|
||||
|
||||
QSize msz = minimumSizeHint();
|
||||
setMinimumSize(qMax(msz.width(), clearButton->sizeHint().height() + frameWidth * 2 + 2),
|
||||
qMax(msz.height(), clearButton->sizeHint().height() /* + frameWidth * 2 + -2*/));
|
||||
|
||||
setPlaceholderText("Search...");
|
||||
setDragEnabled(true);
|
||||
connect(this, SIGNAL(returnPressed()), this, SLOT(searchSubmit()));
|
||||
}
|
||||
|
||||
void SearchBox::resizeEvent(QResizeEvent *)
|
||||
{
|
||||
QSize sz = clearButton->sizeHint();
|
||||
int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
|
||||
clearButton->move(rect().right() - frameWidth - sz.width(),
|
||||
(rect().bottom() + 1 - sz.height())/2);
|
||||
searchButton->move(rect().left() + frameWidth,
|
||||
(rect().bottom() + 1 - sz.height())/2);
|
||||
}
|
||||
|
||||
void SearchBox::updateCloseButton(const QString& text)
|
||||
{
|
||||
if (clearButton->isVisible() && text.isEmpty()) clearQuery();
|
||||
clearButton->setVisible(!text.isEmpty());
|
||||
}
|
||||
|
||||
void SearchBox::searchSubmit()
|
||||
{
|
||||
// return hit / key pressed
|
||||
if (text() != "") {
|
||||
submitQuery(text());
|
||||
}
|
||||
}
|
||||
|
||||
// Drag and drop columns from the chooser...
|
||||
void
|
||||
SearchBox::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (event->mimeData()->data("application/x-columnchooser") != "")
|
||||
event->acceptProposedAction(); // whatever you wanna drop we will try and process!
|
||||
else
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void
|
||||
SearchBox::dropEvent(QDropEvent *event)
|
||||
{
|
||||
QString name = event->mimeData()->data("application/x-columnchooser");
|
||||
// fugly, but it works for BikeScore with the (TM) in it...
|
||||
if (name == "BikeScore?") name = QString("BikeScore™").replace("™", QChar(0x2122));
|
||||
|
||||
// we do very little to the name, just space to _ and lower case it for now...
|
||||
name.replace(' ', '_');
|
||||
insert(name + ":\"\"");
|
||||
}
|
||||
54
src/SearchBox.h
Normal file
54
src/SearchBox.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Mark Liversedge (liversedge@gmail.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _GC_SearchBox_h
|
||||
#define _GC_SearchBox_h
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QDropEvent>
|
||||
|
||||
class QToolButton;
|
||||
|
||||
class SearchBox : public QLineEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SearchBox(QWidget *parent = 0);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *);
|
||||
|
||||
private slots:
|
||||
void updateCloseButton(const QString &text);
|
||||
void searchSubmit();
|
||||
|
||||
// drop column headings from column chooser
|
||||
void dragEnterEvent(QDragEnterEvent *event);
|
||||
void dropEvent(QDropEvent *event);
|
||||
|
||||
signals:
|
||||
void submitQuery(QString);
|
||||
void clearQuery();
|
||||
|
||||
private:
|
||||
QToolButton *clearButton, *searchButton;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -21,6 +21,8 @@
|
||||
<file>images/toolbar/main/home.png</file>
|
||||
<file>images/toolbar/main/measures.png</file>
|
||||
<file>images/toolbar/main/train.png</file>
|
||||
<file>images/toolbar/clear.png</file>
|
||||
<file>images/toolbar/search.png</file>
|
||||
<file>images/maps/cycling_feed.png</file>
|
||||
<file>images/maps/loop.png</file>
|
||||
<file>images/maps/cycling.png</file>
|
||||
@@ -85,7 +87,6 @@
|
||||
<file>images/oxygen/open.png</file>
|
||||
<file>images/toolbar/close-icon.png</file>
|
||||
<file>images/toolbar/save.png</file>
|
||||
<file>images/toolbar/search.png</file>
|
||||
<file>images/toolbar/splash green.png</file>
|
||||
<file>images/toolbar/cut.png</file>
|
||||
<file>images/toolbar/copy.png</file>
|
||||
|
||||
@@ -168,6 +168,13 @@ BOOST_INCLUDE =
|
||||
#VLC_INCLUDE =
|
||||
#VLC_LIBS =
|
||||
|
||||
#If you want search functionality then uncomment the following
|
||||
#two lines once you habve installed clucene developer libraries
|
||||
#and runtimes. See the INSTALL guide for your platform.
|
||||
#CLUCENE_INCLUDE = /usr/include/CLucene
|
||||
#CLUCENE_LIBS = -lclucene
|
||||
|
||||
|
||||
# *** Mac users NOTE ***
|
||||
# On MAC you don't need libvlc since we use the
|
||||
# native QTKit (OSX framework) for video playback
|
||||
|
||||
BIN
src/images/toolbar/clear.png
Normal file
BIN
src/images/toolbar/clear.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 987 B |
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -4,6 +4,7 @@ include( gcconfig.pri )
|
||||
|
||||
TEMPLATE = app
|
||||
TARGET = GoldenCheetah
|
||||
|
||||
!isEmpty( APP_NAME ) { TARGET = $${APP_NAME} }
|
||||
DEPENDPATH += .
|
||||
|
||||
@@ -117,6 +118,14 @@ LIBS += -lm $${LIBZ_LIBS}
|
||||
}
|
||||
}
|
||||
|
||||
!isEmpty( CLUCENE_LIBS ) {
|
||||
INCLUDEPATH += $${CLUCENE_INCLUDE}
|
||||
LIBS += $${CLUCENE_LIBS}
|
||||
DEFINES += GC_HAVE_LUCENE
|
||||
HEADERS += Lucene.h SearchBox.h
|
||||
SOURCES += Lucene.cpp SearchBox.cpp
|
||||
}
|
||||
|
||||
# Mac specific build for
|
||||
# Segmented mac style button (but not used at present)
|
||||
# Video playback using Quicktime Framework
|
||||
|
||||
Reference in New Issue
Block a user