1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-06 14:16:32 +02:00

Refactor the resolver config UI for More Prettiness

Allow resolvers to configure themselves and whatnot
This commit is contained in:
Leo Franchi
2011-04-12 06:56:19 -04:00
parent 4b13467e4c
commit 714c306032
25 changed files with 835 additions and 143 deletions

BIN
data/images/configure.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

View File

@@ -90,8 +90,9 @@ public:
TomahawkWindow* mainWindow() const { return m_mainwindow; }
#endif
void addScriptResolver( const QString& scriptPath );
void removeScriptResolver( const QString& scriptPath );
void enableScriptResolver( const QString& scriptPath );
void disableScriptResolver( const QString& scriptPath );
Tomahawk::ExternalResolver* resolverForPath( const QString& scriptPath );
// PlatformInterface
virtual void activate();
@@ -116,7 +117,7 @@ private:
void startHTTP();
QList<Tomahawk::collection_ptr> m_collections;
QList<Tomahawk::ExternalResolver*> m_scriptResolvers;
QHash<QString, Tomahawk::ExternalResolver*> m_scriptResolvers;
Database* m_database;
ScanManager *m_scanManager;
@@ -143,3 +144,4 @@ private:
};
#endif // TOMAHAWKAPP_H

View File

@@ -74,6 +74,7 @@
<file>./data/images/back.png</file>
<file>./data/images/forward.png</file>
<file>./data/images/music-icon.png</file>
<file>./data/images/configure.png</file>
<file>./data/topbar-radiobuttons.css</file>
<file>./data/icons/tomahawk-icon-16x16.png</file>
<file>./data/icons/tomahawk-icon-32x32.png</file>

View File

@@ -63,6 +63,8 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui}
tomahawktrayicon.cpp
audiocontrols.cpp
settingsdialog.cpp
resolverconfigdelegate.cpp
resolversmodel.cpp
tomahawkwindow.cpp
)
@@ -99,6 +101,9 @@ SET( tomahawkHeadersGui ${tomahawkHeadersGui}
tomahawktrayicon.h
audiocontrols.h
settingsdialog.h
resolverconfigdelegate.h
resolversmodel.h
resolverconfigwrapper.h
tomahawkwindow.h
)

View File

@@ -4,6 +4,7 @@ SET( QT_USE_QTGUI TRUE )
SET( QT_USE_QTSQL TRUE )
SET( QT_USE_QTNETWORK TRUE )
SET( QT_USE_QTXML TRUE )
SET(QT_USE_QTUITOOLS TRUE)
include( ${QT_USE_FILE} )
@@ -21,6 +22,7 @@ set( libSources
album.cpp
collection.cpp
playlist.cpp
resolver.cpp
query.cpp
result.cpp
source.cpp

View File

@@ -22,6 +22,7 @@
#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QWeakPointer>
#include "boost/function.hpp"
#include "boost/bind.hpp"
@@ -43,8 +44,9 @@ class DLLEXPORT FuncTimeout : public QObject
Q_OBJECT
public:
FuncTimeout( int ms, boost::function<void()> func )
FuncTimeout( int ms, boost::function<void()> func, QObject* besafe )
: m_func( func )
, m_watch( QWeakPointer< QObject >( besafe ) )
{
//qDebug() << Q_FUNC_INFO;
QTimer::singleShot( ms, this, SLOT( exec() ) );
@@ -58,12 +60,14 @@ public:
public slots:
void exec()
{
if( !m_watch.isNull() )
m_func();
this->deleteLater();
};
private:
boost::function<void()> m_func;
QWeakPointer< QObject > m_watch;
};
}; // ns

View File

@@ -292,7 +292,7 @@ Pipeline::shunt( const query_ptr& q )
{
incQIDState( q );
// qDebug() << "Shunting in" << lasttimeout << "ms, q:" << q->toString();
new FuncTimeout( lasttimeout, boost::bind( &Pipeline::shunt, this, q ) );
new FuncTimeout( lasttimeout, boost::bind( &Pipeline::shunt, this, q ), this );
}
}
else
@@ -311,8 +311,8 @@ Pipeline::shunt( const query_ptr& q )
bool
Pipeline::resolverSorter( const Resolver* left, const Resolver* right )
{
if( left->weight() == right->weight() )
return left->preference() > right->preference();
if( left->weight() == right->weight() ) // TODO dispatch in parallel
return left;
else
return left->weight() > right->weight();
}

View File

@@ -0,0 +1,116 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk 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 3 of the License, or
* (at your option) any later version.
*
* Tomahawk 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 Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "resolver.h"
#include <QWidget>
#include <QtUiTools/QUiLoader>
#include <QMetaProperty>
#include <QDebug>
#include <QBuffer>
#include <qtemporaryfile.h>
#include <QDir>
#include <QIcon>
QVariant
Tomahawk::ExternalResolver::configMsgFromWidget( QWidget* w )
{
if( !w )
return QVariant();
// generate a qvariantmap of all the widgets in the hierarchy, and for each one include the list of properties and values
QVariantMap widgetMap;
addChildProperties( w, widgetMap );
// qDebug() << "Generated widget variant:" << widgetMap;
return widgetMap;
}
void
Tomahawk::ExternalResolver::addChildProperties( QObject* widget, QVariantMap& m )
{
// recursively add all properties of this widget to the map, then repeat on all children.
// bare QWidgets are boring---so skip them! They have no input that the user can set.
if( !widget || !widget->isWidgetType() )
return;
if( qstrcmp( widget->metaObject()->className(), "QWidget" ) != 0 )
{
// qDebug() << "Adding properties for this:" << widget->metaObject()->className();
// add this widget's properties
QVariantMap props;
for( int i = widget->metaObject()->propertyOffset(); i < widget->metaObject()->propertyCount(); i++ )
{
QString prop = widget->metaObject()->property( i ).name();
QVariant val = widget->property( prop.toLatin1() );
// clean up for QJson....
if( val.canConvert< QPixmap >() || val.canConvert< QImage >() || val.canConvert< QIcon >() )
continue;
props[ prop ] = val;
// qDebug() << QString( "%1: %2" ).arg( prop ).arg( props[ prop ].toString() );
}
m[ widget->objectName() ] = props;
}
// and recurse
foreach( QObject* child, widget->children() )
addChildProperties( child, m );
}
QWidget*
Tomahawk::ExternalResolver::widgetFromData( QByteArray& data, QWidget* parent )
{
if( data.isEmpty() )
return 0;
QUiLoader l;
QBuffer b( &data );
QWidget* w = l.load( &b, parent );
return w;
}
QByteArray
Tomahawk::ExternalResolver::fixDataImagePaths( const QByteArray& data, bool compressed, const QVariantMap& images )
{
// with a list of images and image data, write each to a temp file, replace the path in the .ui file with the temp file path
QString uiFile = QString::fromUtf8( data );
foreach( const QString& filename, images.keys() )
{
if( !uiFile.contains( filename ) ) // make sure the image is used
continue;
QString fullPath = QDir::tempPath() + "/" + filename;
QFile imgF( fullPath );
if( !imgF.open( QIODevice::WriteOnly ) )
{
qWarning() << "Failed to write to temporary image in UI file:" << filename << fullPath;
continue;
}
QByteArray data = images[ filename ].toByteArray();
qDebug() << "expanding data:" << data << compressed;
data = compressed ? qUncompress( QByteArray::fromBase64( data ) ) : QByteArray::fromBase64( data );
imgF.write( data );
imgF.close();
// replace the path to the image with the real path
uiFile.replace( filename, fullPath );
}
return uiFile.toUtf8();
}

View File

@@ -33,6 +33,9 @@
weighted resolver
*/
class QWidget;
namespace Tomahawk
{
@@ -45,12 +48,8 @@ public:
virtual QString name() const = 0;
virtual unsigned int weight() const = 0;
virtual unsigned int preference() const { return 100; };
virtual unsigned int timeout() const = 0;
//virtual QWidget * configUI() { return 0; };
//etc
public slots:
virtual void resolve( const Tomahawk::query_ptr& query ) = 0;
};
@@ -64,10 +63,18 @@ public:
virtual QString filePath() const { return m_filePath; }
virtual QWidget* configUI() const = 0;
virtual void saveConfig() = 0;
public slots:
virtual void stop() = 0;
protected:
QWidget* widgetFromData( QByteArray& data, QWidget* parent = 0 );
QVariant configMsgFromWidget( QWidget* w );
QByteArray fixDataImagePaths( const QByteArray& data, bool compressed, const QVariantMap& images );
private:
void addChildProperties( QObject* parent, QVariantMap& m );
QString m_filePath;
};

View File

@@ -630,17 +630,30 @@ TomahawkSettings::setXmppBotPort( const int port )
void
TomahawkSettings::addScriptResolver(const QString& resolver)
{
setValue( "script/resolvers", scriptResolvers() << resolver );
setValue( "script/resolvers", allScriptResolvers() << resolver );
}
QStringList
TomahawkSettings::scriptResolvers() const
TomahawkSettings::allScriptResolvers() const
{
return value( "script/resolvers" ).toStringList();
}
void
TomahawkSettings::setScriptResolvers( const QStringList& resolver )
TomahawkSettings::setAllScriptResolvers( const QStringList& resolver )
{
setValue( "script/resolvers", resolver );
}
QStringList
TomahawkSettings::enabledScriptResolvers() const
{
return value( "script/loadedresolvers" ).toStringList();
}
void
TomahawkSettings::setEnabledScriptResolvers( const QStringList& resolvers )
{
setValue( "script/loadedresolvers", resolvers );
}

View File

@@ -170,9 +170,12 @@ public:
void setXmppBotPort( const int port );
/// Script resolver settings
QStringList scriptResolvers() const;
void setScriptResolvers( const QStringList& resolver );
QStringList allScriptResolvers() const;
void setAllScriptResolvers( const QStringList& resolvers );
void addScriptResolver( const QString& resolver );
QStringList enabledScriptResolvers() const;
void setEnabledScriptResolvers( const QStringList& resolvers );
signals:
void changed();

View File

@@ -0,0 +1,176 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk 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 3 of the License, or
* (at your option) any later version.
*
* Tomahawk 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 Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "resolverconfigdelegate.h"
#include "resolversmodel.h"
#include "tomahawk/tomahawkapp.h"
#include <QApplication>
#include <QPainter>
#include <QMouseEvent>
#define PADDING 4
ResolverConfigDelegate::ResolverConfigDelegate( QObject* parent )
: QStyledItemDelegate( parent )
, m_configPressed( false )
{
}
void
ResolverConfigDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
QStyleOptionViewItemV4 opt = option;
initStyleOption( &opt, index );
QRect itemRect = opt.rect;
int top = itemRect.top();
QFont name = opt.font;
name.setPointSize( name.pointSize() + 2 );
name.setBold( true );
QFont path = opt.font;
path.setItalic( true );
path.setPointSize( path.pointSize() - 1 );
QFontMetrics bfm( name );
QFontMetrics sfm( path );
// draw the background
const QWidget* w = opt.widget;
QStyle* style = w ? w->style() : QApplication::style();
style->drawPrimitive( QStyle::PE_PanelItemViewItem, &opt, painter, w );
int rightSplit = itemRect.width();
int rectW = opt.rect.height() - 4 * PADDING;
QRect confRect = QRect( rightSplit - rectW - 2 * PADDING, 2 * PADDING + top, rectW, rectW );
// if the resolver has a config widget, paint it first (right-aligned)
if( index.data( ResolversModel::HasConfig ).toBool() ) {
// draw it the same size as the check belox
QStyleOptionToolButton topt;
topt.font = opt.font;
topt.icon = QIcon( RESPATH "images/configure.png" );
topt.iconSize = QSize( 16, 16 );
topt.rect = confRect;
topt.subControls = QStyle::SC_ToolButton;
topt.activeSubControls = QStyle::SC_None;
topt.features = QStyleOptionToolButton::None;
topt.pos = confRect.topLeft();
topt.state = m_configPressed ? QStyle::State_On : QStyle::State_Raised;
if( opt.state & QStyle::State_MouseOver || m_configPressed )
topt.state |= QStyle::State_HasFocus;
style->drawComplexControl( QStyle::CC_ToolButton, &topt, painter, w );
}
// draw check
confRect.moveTo( 2 * PADDING, 2 * PADDING + top );
opt.rect = confRect;
opt.checkState == Qt::Checked ? opt.state |= QStyle::State_On : opt.state |= QStyle::State_Off;
style->drawPrimitive( QStyle::PE_IndicatorViewItemCheck, &opt, painter, w );
itemRect.setX( opt.rect.topRight().x() + PADDING );
QString nameStr = bfm.elidedText( index.data( ResolversModel::ResolverName ).toString(),Qt::ElideRight, rightSplit );
painter->save();
painter->setFont( name );
QRect textRect = itemRect.adjusted( PADDING, PADDING, -PADDING, -PADDING );
textRect.setBottom( itemRect.height() / 2 + top );
painter->drawText( textRect, nameStr );
painter->restore();
QString pathStr = sfm.elidedText( index.data( ResolversModel::ResolverPath ).toString(),Qt::ElideMiddle, rightSplit );
painter->save();
painter->setFont( path );
painter->setBrush( Qt::gray );
textRect.moveTop( itemRect.height() / 2 + top );
painter->drawText( textRect, pathStr );
painter->restore();
}
QSize
ResolverConfigDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
int width = QStyledItemDelegate::sizeHint( option, index ).width();
QStyleOptionViewItemV4 opt = option;
initStyleOption( &opt, index );
QFont name = opt.font;
name.setPointSize( name.pointSize() + 2 );
name.setBold( true );
QFont path = opt.font;
path.setItalic( true );
path.setPointSize( path.pointSize() - 1 );
QFontMetrics bfm( name );
QFontMetrics sfm( path );
return QSize( width, 3 * PADDING + bfm.height() + sfm.height() );
}
bool
ResolverConfigDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
{
// qDebug() << "EDITOR EVENT!" << ( event->type() == QEvent::MouseButtonRelease );
QStyleOptionViewItemV4 viewOpt( option );
initStyleOption( &viewOpt, index );
const QWidget* w = viewOpt.widget;
QStyle* style = w ? w->style() : QApplication::style();
int top = viewOpt.rect.top();
if( event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonDblClick ) {
m_configPressed = false;
int rectW = option.rect.height() - 4 * PADDING;
QRect checkRect = QRect( 2 * PADDING, 2 * PADDING + top, rectW, rectW );
QMouseEvent* me = static_cast< QMouseEvent* >( event );
if( me->button() != Qt::LeftButton || !checkRect.contains( me->pos() ) )
return false;
// eat the double click events inside the check rect
if( event->type() == QEvent::MouseButtonDblClick ) {
return true;
}
Qt::CheckState curState = static_cast< Qt::CheckState >( index.data( Qt::CheckStateRole ).toInt() );
Qt::CheckState newState = curState == Qt::Checked ? Qt::Unchecked : Qt::Checked;
return model->setData( index, newState, Qt::CheckStateRole );
} else if( event->type() == QEvent::MouseButtonPress ) {
int rightSplit = viewOpt.rect.width();
int rectW = viewOpt.rect.height() - 4 * PADDING;
QRect confRect = QRect( rightSplit - rectW - 2 * PADDING, 2 * PADDING + top, rectW, rectW );
QMouseEvent* me = static_cast< QMouseEvent* >( event );
if( me->button() == Qt::LeftButton && confRect.contains( me->pos() ) ) {
m_configPressed = true;
emit openConfig( index.data( ResolversModel::ResolverPath ).toString() );
return true;
}
}
return QStyledItemDelegate::editorEvent( event, model, option, index );
}

View File

@@ -0,0 +1,42 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk 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 3 of the License, or
* (at your option) any later version.
*
* Tomahawk 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 Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RESOLVERCONFIGDELEGATE_H
#define RESOLVERCONFIGDELEGATE_H
#include <QStyledItemDelegate>
class ResolverConfigDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit ResolverConfigDelegate(QObject* parent = 0);
virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
virtual bool editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index );
signals:
void openConfig( const QString& resolverPath );
private:
bool m_configPressed;
};
#endif // RESOLVERCONFIGDELEGATE_H

View File

View File

@@ -0,0 +1,68 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk 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 3 of the License, or
* (at your option) any later version.
*
* Tomahawk 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 Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RESOLVER_CONFIG_WRAPPER
#define RESOLVER_CONFIG_WRAPPER
#include <QDialog>
#include <QDialogButtonBox>
#include <QVBoxLayout>
class ResolverConfigWrapper : public QDialog
{
Q_OBJECT
public:
ResolverConfigWrapper( QWidget* conf, const QString& title, QWidget* parent ) : QDialog( parent ), m_widget( conf )
{
setWindowTitle( title );
QVBoxLayout* v = new QVBoxLayout( this );
v->addWidget( m_widget );
QDialogButtonBox* buttons = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this );
connect( buttons, SIGNAL( clicked( QAbstractButton*) ), this, SLOT( closed( QAbstractButton* ) ) );
connect( this, SIGNAL( rejected() ), this, SLOT( rejected() ) );
v->addWidget( buttons );
setLayout( v );
}
public slots:
void closed( QAbstractButton* b )
{
// let the config widget live to see another day
layout()->removeWidget( m_widget );
m_widget->setParent( 0 );
QDialogButtonBox* buttons = qobject_cast< QDialogButtonBox* >( sender() );
if( buttons->standardButton( b ) == QDialogButtonBox::Ok )
done( QDialog::Accepted );
else
done( QDialog::Rejected );
}
// we get a rejected() signal emitted if the user presses escape (and no clicked() signal )
void rejected()
{
layout()->removeWidget( m_widget );
m_widget->setParent( 0 );
}
private:
QWidget* m_widget;
};
#endif

View File

@@ -49,9 +49,8 @@ QtScriptResolver::QtScriptResolver( const QString& scriptPath )
m_name = m.value( "name" ).toString();
m_weight = m.value( "weight", 0 ).toUInt();
m_timeout = m.value( "timeout", 25 ).toUInt() * 1000;
m_preference = m.value( "preference", 0 ).toUInt();
qDebug() << Q_FUNC_INFO << m_name << m_weight << m_timeout << m_preference;
qDebug() << Q_FUNC_INFO << m_name << m_weight << m_timeout;
m_ready = true;
Tomahawk::Pipeline::instance()->addResolver( this );

View File

@@ -68,9 +68,10 @@ public:
virtual QString name() const { return m_name; }
virtual unsigned int weight() const { return m_weight; }
virtual unsigned int preference() const { return m_preference; }
virtual unsigned int timeout() const { return m_timeout; }
virtual QWidget* configUI() const { return 0; } // TODO support properly for qtscript resolvers too!
virtual void saveConfig() {}
public slots:
virtual void resolve( const Tomahawk::query_ptr& query );
virtual void stop();
@@ -82,7 +83,7 @@ private:
ScriptEngine* m_engine;
QString m_name;
unsigned int m_weight, m_preference, m_timeout;
unsigned int m_weight, m_timeout;
bool m_ready, m_stopped;
};

View File

@@ -49,6 +49,9 @@ ScriptResolver::~ScriptResolver()
stop();
Tomahawk::Pipeline::instance()->removeResolver( this );
if( !m_configWidget.isNull() )
delete m_configWidget.data();
}
@@ -94,8 +97,6 @@ ScriptResolver::sendMsg( const QByteArray& msg )
{
qDebug() << Q_FUNC_INFO << m_ready << msg << msg.length();
if( !m_ready ) return;
quint32 len;
qToBigEndian( msg.length(), (uchar*) &len );
m_proc.write( (const char*) &len, 4 );
@@ -121,6 +122,9 @@ ScriptResolver::handleMsg( const QByteArray& msg )
{
doSetup( m );
return;
} else if( msgtype == "confwidget" ) {
setupConfWidget( m );
return;
}
if( msgtype == "results" )
@@ -212,7 +216,7 @@ ScriptResolver::resolve( const Tomahawk::query_ptr& query )
sendMsg( msg );
m_queryState.insert( query->id(), 1 );
new Tomahawk::FuncTimeout( m_timeout, boost::bind( &ScriptResolver::onTimeout, this, query ) );
new Tomahawk::FuncTimeout( m_timeout, boost::bind( &ScriptResolver::onTimeout, this, query ), this );
}
@@ -223,17 +227,54 @@ ScriptResolver::doSetup( const QVariantMap& m )
m_name = m.value( "name" ).toString();
m_weight = m.value( "weight", 0 ).toUInt();
m_timeout = m.value( "timeout", 25 ).toUInt() * 1000;
m_preference = m.value( "preference", 0 ).toUInt();
qDebug() << "SCRIPT" << filePath() << "READY," << endl
<< "name" << m_name << endl
<< "weight" << m_weight << endl
<< "timeout" << m_timeout << endl
<< "preference" << m_preference;
<< "timeout" << m_timeout;
m_ready = true;
Tomahawk::Pipeline::instance()->addResolver( this );
}
void
ScriptResolver::setupConfWidget( const QVariantMap& m )
{
bool compressed = m.value( "compressed", "false" ).toString() == "true";
qDebug() << "Resolver has a preferences widget! compressed?" << compressed << m;
QByteArray uiData = m[ "widget" ].toByteArray();
if( compressed )
uiData = qUncompress( QByteArray::fromBase64( uiData ) );
else
uiData = QByteArray::fromBase64( uiData );
if( m.contains( "images" ) )
uiData = fixDataImagePaths( uiData, compressed, m[ "images" ].toMap() );
m_configWidget = QWeakPointer< QWidget >( widgetFromData( uiData, 0 ) );
}
void
ScriptResolver::saveConfig()
{
Q_ASSERT( !m_configWidget.isNull() );
QVariantMap m;
m.insert( "_msgtype", "setpref" );
QVariant widgets = configMsgFromWidget( m_configWidget.data() );
m.insert( "widgets", widgets );
QByteArray data = m_serializer.serialize( m );
sendMsg( data );
}
QWidget* ScriptResolver::configUI() const
{
if( m_configWidget.isNull() )
return 0;
else
return m_configWidget.data();
}
void
ScriptResolver::stop()

View File

@@ -29,6 +29,7 @@
#include "query.h"
#include "result.h"
class QWidget;
class ScriptResolver : public Tomahawk::ExternalResolver
{
Q_OBJECT
@@ -42,6 +43,9 @@ public:
virtual unsigned int preference() const { return m_preference; }
virtual unsigned int timeout() const { return m_timeout; }
virtual QWidget* configUI() const;
virtual void saveConfig();
signals:
void finished();
@@ -60,10 +64,12 @@ private:
void handleMsg( const QByteArray& msg );
void sendMsg( const QByteArray& msg );
void doSetup( const QVariantMap& m );
void setupConfWidget( const QVariantMap& m );
QProcess m_proc;
QString m_name;
unsigned int m_weight, m_preference, m_timeout, m_num_restarts;
QWeakPointer< QWidget > m_configWidget;
quint32 m_msgsize;
QByteArray m_msg;

154
src/resolversmodel.cpp Normal file
View File

@@ -0,0 +1,154 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk 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 3 of the License, or
* (at your option) any later version.
*
* Tomahawk 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 Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "resolversmodel.h"
#include <QFileInfo>
#include <tomahawksettings.h>
#include <tomahawk/tomahawkapp.h>
ResolversModel::ResolversModel( const QStringList& allResolvers, const QStringList& enabledResolvers, QObject* parent )
: QAbstractListModel( parent )
, m_allResolvers( allResolvers )
, m_enabledResolvers( enabledResolvers )
{
// do some sanity checking just in case
bool changed = false;
foreach( const QString& l, m_enabledResolvers ) {
if( !m_allResolvers.contains( l ) ) {
m_enabledResolvers.removeAll( l );
changed = true;
}
}
if( changed )
TomahawkSettings::instance()->setEnabledScriptResolvers( m_enabledResolvers );
}
ResolversModel::~ResolversModel()
{
}
QVariant
ResolversModel::data( const QModelIndex& index, int role ) const
{
if( !index.isValid() )
return QVariant();
switch( role )
{
case Qt::DisplayRole:
case ResolversModel::ResolverName:
{
QFileInfo info( m_allResolvers.at( index.row() ) );
return info.baseName();
}
case ResolversModel::ResolverPath:
return m_allResolvers.at( index.row() );
case ResolversModel::HasConfig:
if( Tomahawk::ExternalResolver* r = TomahawkApp::instance()->resolverForPath( m_allResolvers.at( index.row() ) ) ) // if we have one, it means we are loaded too!
return r->configUI() != 0;
return false;
case Qt::CheckStateRole:
return m_enabledResolvers.contains( m_allResolvers.at( index.row() ) ) ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
}
bool
ResolversModel::setData( const QModelIndex& index, const QVariant& value, int role )
{
if( role == Qt::CheckStateRole ) {
Qt::CheckState state = static_cast< Qt::CheckState >( value.toInt() );
QString resolver = m_allResolvers.at( index.row() );
if( state == Qt::Checked && !m_enabledResolvers.contains( resolver ) ) {
m_enabledResolvers.append( resolver );
TomahawkApp::instance()->enableScriptResolver( resolver );
} else if( state == Qt::Unchecked ) {
m_enabledResolvers.removeAll( resolver );
TomahawkApp::instance()->disableScriptResolver( resolver );
}
dataChanged( index, index );
return true;
}
return false;
}
int
ResolversModel::rowCount( const QModelIndex& parent ) const
{
return m_allResolvers.size();
}
int
ResolversModel::columnCount(const QModelIndex& parent) const
{
return 1;
}
Qt::ItemFlags
ResolversModel::flags( const QModelIndex& index ) const
{
return QAbstractItemModel::flags(index) | Qt::ItemIsUserCheckable;
}
void
ResolversModel::addResolver( const QString& resolver, bool enable )
{
beginInsertRows( QModelIndex(), m_allResolvers.count(), m_allResolvers.count() );
m_allResolvers << resolver;
if( enable )
m_enabledResolvers << resolver;
endInsertRows();
}
void
ResolversModel::removeResolver( const QString& resolver )
{
for( int i = 0; i < m_allResolvers.count(); i++ ) {
if( m_allResolvers.at( i ) == resolver ) {
beginRemoveRows( QModelIndex(), i, i );
m_allResolvers.takeAt( i );
endRemoveRows();
}
}
m_enabledResolvers.removeAll( resolver );
}
QStringList
ResolversModel::allResolvers() const
{
return m_allResolvers;
}
QStringList
ResolversModel::enabledResolvers() const
{
return m_enabledResolvers;
}

55
src/resolversmodel.h Normal file
View File

@@ -0,0 +1,55 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk 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 3 of the License, or
* (at your option) any later version.
*
* Tomahawk 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 Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RESOLVERSMODEL_H
#define RESOLVERSMODEL_H
#include <QModelIndex>
#include <QStringList>
class ResolversModel : public QAbstractListModel
{
public:
enum Roles {
ResolverName = Qt::UserRole + 15,
ResolverPath = Qt::UserRole + 16,
HasConfig = Qt::UserRole + 17
};
explicit ResolversModel( const QStringList& allResolvers, const QStringList& enabledResolvers, QObject* parent = 0 );
virtual ~ResolversModel();
virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const;
virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const;
virtual int columnCount( const QModelIndex& parent ) const;
virtual Qt::ItemFlags flags(const QModelIndex& index) const;
virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
void addResolver( const QString& resolver, bool enable = false );
void removeResolver( const QString& resolver );
QStringList allResolvers() const;
QStringList enabledResolvers() const;
private:
QStringList m_allResolvers;
QStringList m_enabledResolvers;
};
#endif // RESOLVERSMODEL_H

View File

@@ -24,6 +24,7 @@
#include <QFileDialog>
#include <QMessageBox>
#include <QNetworkProxy>
#include <QVBoxLayout>
#ifdef LIBLASTFM_FOUND
#include <lastfm/ws.h>
@@ -39,6 +40,9 @@
#include "sip/SipHandler.h"
#include <database/database.h>
#include "scanmanager.h"
#include "resolverconfigdelegate.h"
#include "resolversmodel.h"
#include "resolverconfigwrapper.h"
static QString
md5( const QByteArray& src )
@@ -47,7 +51,6 @@ md5( const QByteArray& src )
return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' );
}
SettingsDialog::SettingsDialog( QWidget *parent )
: QDialog( parent )
, ui( new Ui::SettingsDialog )
@@ -99,12 +102,13 @@ SettingsDialog::SettingsDialog( QWidget *parent )
// SCRIPT RESOLVER
ui->removeScript->setEnabled( false );
foreach( const QString& resolver, s->scriptResolvers() ) {
QFileInfo info( resolver );
ui->scriptList->addTopLevelItem( new QTreeWidgetItem( QStringList() << info.baseName() << resolver ) );
ResolverConfigDelegate* del = new ResolverConfigDelegate( this );
connect( del, SIGNAL( openConfig( QString ) ), this, SLOT( openResolverConfig( QString ) ) );
ui->scriptList->setItemDelegate( del );
m_resolversModel = new ResolversModel( s->allScriptResolvers(), s->enabledScriptResolvers(), this );
ui->scriptList->setModel( m_resolversModel );
}
connect( ui->scriptList, SIGNAL( itemClicked( QTreeWidgetItem*, int ) ), this, SLOT( scriptSelectionChanged() ) );
connect( ui->scriptList->selectionModel(), SIGNAL( selectionChanged( QItemSelection,QItemSelection ) ), this, SLOT( scriptSelectionChanged() ) );
connect( ui->addScript, SIGNAL( clicked( bool ) ), this, SLOT( addScriptResolver() ) );
connect( ui->removeScript, SIGNAL( clicked( bool ) ), this, SLOT( removeScriptResolver() ) );
@@ -143,12 +147,8 @@ SettingsDialog::~SettingsDialog()
s->setLastFmUsername( ui->lineEditLastfmUsername->text() );
s->setLastFmPassword( ui->lineEditLastfmPassword->text() );
QStringList resolvers;
for( int i = 0; i < ui->scriptList->topLevelItemCount(); i++ )
{
resolvers << ui->scriptList->topLevelItem( i )->data( 1, Qt::DisplayRole ).toString();
}
s->setScriptResolvers( resolvers );
s->setAllScriptResolvers( m_resolversModel->allResolvers() );
s->setEnabledScriptResolvers( m_resolversModel->enabledResolvers() );
s->applyChanges();
}
@@ -341,10 +341,8 @@ SettingsDialog::addScriptResolver()
{
QString resolver = QFileDialog::getOpenFileName( this, tr( "Load script resolver file" ), qApp->applicationDirPath() );
if( !resolver.isEmpty() ) {
QFileInfo info( resolver );
ui->scriptList->addTopLevelItem( new QTreeWidgetItem( QStringList() << info.baseName() << resolver ) );
TomahawkApp::instance()->addScriptResolver( resolver );
m_resolversModel->addResolver( resolver, true );
TomahawkApp::instance()->enableScriptResolver( resolver );
}
}
@@ -353,21 +351,35 @@ void
SettingsDialog::removeScriptResolver()
{
// only one selection
if( !ui->scriptList->selectedItems().isEmpty() ) {
QString resolver = ui->scriptList->selectedItems().first()->data( 1, Qt::DisplayRole ).toString();
delete ui->scriptList->takeTopLevelItem( ui->scriptList->indexOfTopLevelItem( ui->scriptList->selectedItems().first() ) );
if( !ui->scriptList->selectionModel()->selectedIndexes().isEmpty() ) {
QString resolver = ui->scriptList->selectionModel()->selectedIndexes().first().data( ResolversModel::ResolverPath ).toString();
m_resolversModel->removeResolver( resolver );
TomahawkApp::instance()->removeScriptResolver( resolver );
TomahawkApp::instance()->disableScriptResolver( resolver );
}
}
void
SettingsDialog::scriptSelectionChanged()
{
if( !ui->scriptList->selectedItems().isEmpty() ) {
if( !ui->scriptList->selectionModel()->selectedIndexes().isEmpty() ) {
ui->removeScript->setEnabled( true );
} else {
ui->removeScript->setEnabled( false );
}
}
void
SettingsDialog::openResolverConfig( const QString& resolver )
{
Tomahawk::ExternalResolver* r = TomahawkApp::instance()->resolverForPath( resolver );
if( r && r->configUI() ) {
ResolverConfigWrapper dialog( r->configUI(), "Resolver Config", this );
QWeakPointer< ResolverConfigWrapper > watcher( &dialog );
int ret = dialog.exec();
if( !watcher.isNull() && ret == QDialog::Accepted ) {
// send changed config to resolver
r->saveConfig();
}
}
}

View File

@@ -21,6 +21,7 @@
#include <QDialog>
class ResolversModel;
class QNetworkReply;
namespace Ui
@@ -71,6 +72,7 @@ private slots:
void addScriptResolver();
void scriptSelectionChanged();
void removeScriptResolver();
void openResolverConfig( const QString& );
private:
Ui::SettingsDialog* ui;
@@ -78,6 +80,7 @@ private:
ProxyDialog m_proxySettings;
bool m_rejected;
QNetworkReply* m_testLastFmQuery;
ResolversModel* m_resolversModel;
};
#endif // SETTINGSDIALOG_H

View File

@@ -23,7 +23,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
<number>4</number>
</property>
<widget class="QWidget" name="tabJabber">
<attribute name="title">
@@ -590,7 +590,7 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QTreeWidget" name="scriptList">
<widget class="QTreeView" name="scriptList">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
@@ -600,40 +600,18 @@
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="animated">
<bool>true</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<attribute name="headerCascadingSectionResizes">
<property name="headerHidden">
<bool>true</bool>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>150</number>
</attribute>
<attribute name="headerHighlightSections">
<bool>true</bool>
</attribute>
<attribute name="headerStretchLastSection">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
<column>
<property name="text">
<string notr="true">2</string>
</property>
</column>
</widget>
</item>
<item>

View File

@@ -289,7 +289,7 @@ TomahawkApp::~TomahawkApp()
qDebug() << Q_FUNC_INFO;
// stop script resolvers
foreach( Tomahawk::ExternalResolver* r, m_scriptResolvers )
foreach( Tomahawk::ExternalResolver* r, m_scriptResolvers.values() )
{
delete r;
}
@@ -415,35 +415,39 @@ TomahawkApp::setupPipeline()
Pipeline::instance()->addResolver( new DatabaseResolver( 100 ) );
// load script resolvers
foreach( QString resolver, TomahawkSettings::instance()->scriptResolvers() )
addScriptResolver( resolver );
foreach( QString resolver, TomahawkSettings::instance()->enabledScriptResolvers() )
enableScriptResolver( resolver );
}
void
TomahawkApp::addScriptResolver( const QString& path )
TomahawkApp::enableScriptResolver( const QString& path )
{
const QFileInfo fi( path );
if ( fi.suffix() == "js" || fi.suffix() == "script" )
m_scriptResolvers << new QtScriptResolver( path );
m_scriptResolvers.insert( path, new QtScriptResolver( path ) );
else
m_scriptResolvers << new ScriptResolver( path );
m_scriptResolvers.insert( path, new ScriptResolver( path ) );
}
void
TomahawkApp::removeScriptResolver( const QString& path )
TomahawkApp::disableScriptResolver( const QString& path )
{
foreach( Tomahawk::ExternalResolver* r, m_scriptResolvers )
if( m_scriptResolvers.contains( path ) )
{
if( r->filePath() == path )
{
m_scriptResolvers.removeAll( r );
Tomahawk::ExternalResolver* r = m_scriptResolvers.take( path );
connect( r, SIGNAL( finished() ), r, SLOT( deleteLater() ) );
r->stop();
return;
}
}
}
Tomahawk::ExternalResolver*
TomahawkApp::resolverForPath( const QString& scriptPath )
{
return m_scriptResolvers.value( scriptPath, 0 );
}