massive change to AbstractRunner API, but now at least it is hopefully future proof and we won't have to change it in BIC ways after 4.0

introduces two new classes: Plasma::SearchContext and Plasma::SearchAction

benefits include:
 - well, future proofing =)
 - the ability to eventually allow runners that mutate the search
 - multiple exact matches per runner
 - 'executing' of informational runners (useful for, e.g., the calculator)
 - being able to centralize expensive operations such a KUriFilter actions
 - simplifies writing runners *dramatically*

svn path=/trunk/KDE/kdebase/workspace/libs/plasma/; revision=731253
This commit is contained in:
Aaron J. Seigo 2007-10-31 04:44:09 +00:00
parent 14ce8b1669
commit e099821b71
2 changed files with 382 additions and 130 deletions

View File

@ -19,34 +19,249 @@
#include "abstractrunner.h"
#include <QAction>
#include <KActionCollection>
#include <KServiceTypeTrader>
#include <KDebug>
#include <KMimeType>
#include <KServiceTypeTrader>
#include <KUriFilterData>
namespace Plasma
{
class SearchContext::Private
{
public:
Private()
: type(SearchContext::UnknownType)
{
}
void resetState()
{
qDeleteAll(info);
info.clear();
qDeleteAll(exact);
exact.clear();
qDeleteAll(possible);
possible.clear();
type = SearchContext::UnknownType;
term.clear();
mimetype.clear();
}
QList<SearchAction *> info;
QList<SearchAction *> exact;
QList<SearchAction *> possible;
QString term;
QString mimetype;
SearchContext::Type type;
};
class SearchAction::Private
{
public:
Private(SearchContext* s, AbstractRunner *r)
: search(s),
runner(r),
type(SearchAction::ExactMatch),
relevance(1)
{
}
SearchContext *search;
AbstractRunner *runner;
SearchAction::Type type;
QString mimetype;
qreal relevance;
};
class AbstractRunner::Private
{
public:
Private( AbstractRunner* runner ) :
exactMatch( 0 ),
actions( new KActionCollection( runner ) )
{
delete exactMatch;
actions->clear();
}
QAction* exactMatch;
KActionCollection* actions;
// FIXME: it's a bit lame to keep a copy of the term in each runner
QString term;
bool hasMatchOptions;
bool hasConfig;
};
AbstractRunner::AbstractRunner( QObject* parent )
: QObject( parent ),
d( new Private( this ) )
SearchContext::SearchContext(QObject *parent)
: QObject(parent),
d(new Private)
{
}
SearchContext::~SearchContext()
{
delete d;
}
void SearchContext::setTerm(const QString &term)
{
d->resetState();
if (term.isEmpty()) {
return;
}
d->term = term;
//FIXME: this is insanely slow =/
KUriFilterData filter(term);
bool filtered = KUriFilter::self()->filterUri(filter);
if (filtered) {
switch (filter.uriType()) {
case KUriFilterData::LocalDir:
d->type = Directory;
d->mimetype = "inode/folder";
break;
case KUriFilterData::LocalFile: {
d->type = File;
KMimeType::Ptr mimetype = KMimeType::findByPath(filter.uri().path());
if (mimetype) {
d->mimetype = mimetype->name();
}
break;
}
case KUriFilterData::NetProtocol:
kDebug() << "term is a network protocol?" << term << filter.uriType();
d->type = NetworkLocation;
break;
case KUriFilterData::Executable:
d->type = Executable;
break;
case KUriFilterData::Shell:
d->type = ShellCommand;
break;
case KUriFilterData::Help:
d->type = Help;
break;
default:
break;
}
}
}
QString SearchContext::term() const
{
return d->term;
}
SearchContext::Type SearchContext::type() const
{
return d->type;
}
QString SearchContext::mimetype() const
{
return d->mimetype;
}
SearchAction* SearchContext::addInformationalMatch(AbstractRunner *runner)
{
SearchAction *action = new SearchAction(this, runner);
action->setType(SearchAction::InformationalMatch);
d->info.append(action);
return action;
}
SearchAction* SearchContext::addExactMatch(AbstractRunner *runner)
{
SearchAction *action = new SearchAction(this, runner);
action->setType(SearchAction::ExactMatch);
d->exact.append(action);
return action;
}
SearchAction* SearchContext::addPossibleMatch(AbstractRunner *runner)
{
SearchAction *action = new SearchAction(this, runner);
action->setType(SearchAction::PossibleMatch);
d->possible.append(action);
return action;
}
QList<SearchAction *> SearchContext::informationalMatches() const
{
return d->info;
}
QList<SearchAction *> SearchContext::exactMatches() const
{
return d->exact;
}
QList<SearchAction *> SearchContext::possibleMatches() const
{
return d->possible;
}
SearchAction::SearchAction(SearchContext *search, AbstractRunner *runner)
: QAction(search),
d(new Private(search, runner))
{
connect(this, SIGNAL(triggered(bool)), this, SLOT(exec()));
}
SearchAction::~SearchAction()
{
delete d;
}
void SearchAction::setType(Type type)
{
d->type = type;
}
SearchAction::Type SearchAction::type() const
{
return d->type;
}
void SearchAction::setMimetype(const QString &mimetype)
{
d->mimetype = mimetype;
}
QString SearchAction::mimetype() const
{
return d->mimetype.isEmpty() ? d->search->mimetype() : d->mimetype;
}
QString SearchAction::term() const
{
return d->search->term();
}
void SearchAction::setRelevance(qreal relevance)
{
d->relevance = qMax(0.0, qMin(1.0, relevance));
}
qreal SearchAction::relevance() const
{
return d->relevance;
}
AbstractRunner* SearchAction::runner() const
{
return d->runner;
}
bool SearchAction::operator<(const SearchAction& other) const
{
return d->relevance < other.d->relevance;
}
void SearchAction::exec()
{
//TODO: this could be dangerous if the runner is deleted behind our backs.
d->runner->exec(this);
}
AbstractRunner::AbstractRunner(QObject* parent)
: QObject(parent),
d(new Private())
{
}
@ -55,63 +270,37 @@ AbstractRunner::~AbstractRunner()
delete d;
}
bool AbstractRunner::hasOptions()
bool AbstractRunner::hasMatchOptions()
{
return false;
return d->hasMatchOptions;
}
QWidget* AbstractRunner::options()
void AbstractRunner::setHasMatchOptions(bool hasMatchOptions)
{
return 0;
d->hasMatchOptions = hasMatchOptions;
}
QAction* AbstractRunner::exactMatch( )
void AbstractRunner::createMatchOptions(QWidget* parent)
{
return d->exactMatch;
Q_UNUSED(parent)
}
QAction* AbstractRunner::exactMatch( const QString& term )
bool AbstractRunner::canBeConfigured()
{
delete d->exactMatch;
d->term.clear();
d->exactMatch = accepts( term );
if ( d->exactMatch ) {
d->term = term;
connect( d->exactMatch, SIGNAL( triggered() ),
this, SLOT( runExactMatch() ) );
}
return d->exactMatch;
return d->hasConfig;
}
KActionCollection* AbstractRunner::matches( const QString& term, int max, int offset )
void AbstractRunner::setCanBeConfigured(bool hasConfig)
{
d->actions->clear();
fillMatches( d->actions, term, max, offset );
return d->actions;
d->hasConfig = hasConfig;
}
void AbstractRunner::fillMatches( KActionCollection* matches,
const QString& term,
int max, int offset )
void AbstractRunner::exec(Plasma::SearchAction *action)
{
Q_UNUSED( matches );
Q_UNUSED( term );
Q_UNUSED( max );
Q_UNUSED( offset );
Q_UNUSED(action)
}
void AbstractRunner::runExactMatch()
{
if (!d->exactMatch) {
return;
}
exec(d->exactMatch, d->term);
}
AbstractRunner::List AbstractRunner::loadRunners( QWidget* parent )
AbstractRunner::List AbstractRunner::loadRunners(QObject* parent)
{
List firstRunners;
List runners;
@ -122,7 +311,7 @@ AbstractRunner::List AbstractRunner::loadRunners( QWidget* parent )
foreach (KService::Ptr service, offers) {
AbstractRunner* runner = service->createInstance<AbstractRunner>(parent, QVariantList(), &error);
if (runner) {
kDebug() << "loaded runner : " << service->name();
//kDebug() << "loaded runner : " << service->name();
QString phase = service->property("X-Plasma-RunnerPhase").toString();
if (phase == "last") {
lastRunners.append(runner);

View File

@ -20,8 +20,9 @@
#ifndef RUNNER_H
#define RUNNER_H
#include <QtCore/QObject>
#include <QtCore/QList>
#include <QAction>
#include <QList>
#include <QObject>
#include <plasma/plasma_export.h>
@ -31,6 +32,114 @@ class QAction;
namespace Plasma
{
class AbstractRunner;
class SearchAction;
class PLASMA_EXPORT SearchContext : public QObject
{
Q_OBJECT
public:
enum Type { UnknownType = 0,
Directory,
File,
NetworkLocation,
Executable,
ShellCommand,
Help
};
explicit SearchContext(QObject *parent = 0);
~SearchContext();
void setTerm(const QString&);
QString term() const;
Type type() const;
QString mimetype() const;
SearchAction* addInformationalMatch(AbstractRunner *runner);
SearchAction* addExactMatch(AbstractRunner *runner);
SearchAction* addPossibleMatch(AbstractRunner *runner);
QList<SearchAction *> informationalMatches() const;
QList<SearchAction *> exactMatches() const;
QList<SearchAction *> possibleMatches() const;
private:
class Private;
Private * const d;
};
class PLASMA_EXPORT SearchAction : public QAction
{
Q_OBJECT
public:
enum Type { InformationalMatch,
ExactMatch,
PossibleMatch };
SearchAction(SearchContext *search, AbstractRunner *runner);
~SearchAction();
/**
* Sets the type of match this action represents.
*/
void setType(Type type);
/**
* The type of action this is. Defaults to ExactMatch.
*/
Type type() const;
/**
* Sets the mimetype, if any, associated with this match
*
* @arg mimetype the mimetype
*/
void setMimetype(const QString &mimetype);
/**
* The mimetype associated with this action, if any
*/
QString mimetype() const;
/**
* The search term that triggered this action
*/
QString term() const;
/**
* Sets the relevance of this action for the search
* it was created for.
*
* @param relevance a number between 0 and 1.
*/
void setRelevance(qreal relevance);
/**
* The relevance of this action to the search. By default,
* the relevance is 1.
*
* @return a number between 0 and 1
*/
qreal relevance() const;
/**
* The runner associated with this action
*/
AbstractRunner* runner() const;
bool operator<(const SearchAction& other) const;
protected Q_SLOTS:
void exec();
private:
class Private;
Private * const d;
};
/**
* A abstract super-class for Plasma Runners
*/
@ -41,12 +150,17 @@ class PLASMA_EXPORT AbstractRunner : public QObject
public:
typedef QList<AbstractRunner*> List;
/**
* Static method is called to load and get a list available of Runners.
*/
static List loadRunners(QObject* parent);
/**
* Constrcuts an Runner object. Since AbstractRunner has pure virtuals,
* this constructor can not be called directly. Rather a subclass must
* be created
*/
explicit AbstractRunner( QObject* parent = 0 );
explicit AbstractRunner(QObject* parent = 0);
virtual ~AbstractRunner();
/**
@ -64,101 +178,50 @@ class PLASMA_EXPORT AbstractRunner : public QObject
*
* Ownership of the action passes to the AbstractRunner class.
*/
QAction* exactMatch(const QString& command);
/**
* @return the last action generated by exactMatch( const QString& )
*/
QAction* exactMatch();
/**
* Requests the runner to find possible matches for the search term.
* Includes basic results paging controls via max and offset.
*
* @param term the search string to use
* @param max maximum number of matches to return
* @param offset the offset into the matched set to start at
*
* @return the collection of actions representing the potential matches
**/
KActionCollection* matches( const QString& term, int max, int offset );
virtual void match(Plasma::SearchContext *search) = 0;
/**
* If the runner has options that the user can interact with to modify
* what happens when exec or one of the actions created in fillMatches
* is called, the runner should return true
*/
virtual bool hasOptions( );
bool hasMatchOptions();
/**
* If the hasOptions() returns true, this method will be called to get
* the widget displaying the options the user can interact with.
* If the hasMatchOptions() returns true, this method will be called to get
* the widget displaying the options the user can interact with to modify
* the behaviour of what happens when a given match is selected.
*
* @param widget the parent of the options widgets.
*/
virtual QWidget* options();
virtual void createMatchOptions(QWidget* widget);
/**
* Static method is called to load and get a list available of Runners.
* If the runner itself has configuration options, this method returns true
*/
static List loadRunners( QWidget* parent );
bool canBeConfigured();
Q_SIGNALS:
/**
* When emitted, the interface will update itself to show the new
* matches. This is meant to be used by asynchronous runners that will
* only be able to start a query on fillMatches being called with
* response (and therefore matches) coming later
* Called whenever an exact or possible match associated with this
* runner is triggered.
*/
void matchesUpdated( KActionCollection* matches );
virtual void exec(Plasma::SearchAction *action);
protected:
/**
* This method is called when there is text input to match. The runner
* should fill the matches action collection with one action per match
* to a maximium of max matches starting at offset in the data set
* If the action is informational only and should not be executed,
* disable the action with setEnabled( false ).
*
* @param matches the action collection to add matches to
* @param term the current search term
* @param max the maximum number of results to return
* @param offset the number of initial results to skip,
* used in conjunction with max
* Sets whether or not the the runner has options for matches
*/
virtual void fillMatches( KActionCollection* matches,
const QString& term,
int max, int offset );
void setHasMatchOptions(bool hasMatchOptions);
/**
* If the runner can run precisely this term, return a QAction, else
* return 0. The first runner that returns a QAction will be the
* default runner. Other runner's actions will be suggested in the
* interface. Non-exact matches should be offered via findMatches.
* The action will be activated if the user selects it.
*
* @param term the current search term
* Sets whether or not the runner has configuration options itself
*/
virtual QAction* accepts(const QString& term) = 0;
/**
* Take action on the command. What this means is dependant on the
* particular runner implementation, e.g. some runners may treat
* command as a shell command, while others may treat it as an
* equation or a user name or ...
*
* This will be called automatically when the exact match is
* selected.
*
* @param action the QAction provided via exactMatch
* @param command the full command string
*/
virtual bool exec(QAction* action, const QString& command) = 0;
void setCanBeConfigured(bool canBeConfigured);
private:
class Private;
Private* const d;
private Q_SLOTS:
void runExactMatch();
};
} // Plasma namespace