1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-09-20 19:01:34 +02:00

Compare commits

..

9 Commits
0.1.0 ... win7

Author SHA1 Message Date
Leo Franchi
9a58b9072d test win7 translucency 2011-06-01 19:22:26 -04:00
Leo Franchi
b4e0674e2b SHow initial config dialog as sheet on osx, WIP 2011-06-01 17:41:44 -04:00
Christian Muehlhaeuser
aeedf286eb * Added Pipeline status view. WIP. 2011-06-01 04:21:35 +02:00
Christian Muehlhaeuser
c52e7e4cdb * Allow seeking in songs - if possible. 2011-06-01 04:21:01 +02:00
Leo Franchi
b356798229 Add an 'Add to my ' option for non-local playlists to copy locally 2011-05-31 22:10:37 -04:00
Leo Franchi
615b2ff5cb And remove cruft. 2011-05-31 18:21:41 -04:00
Leo Franchi
7176b7f0a9 Add the SPMediaKeyTap thirdparty lib. 2011-05-31 18:20:29 -04:00
Leo Franchi
5cd9e49629 Add support for SPMediaKeyTap.
This lets tomahawk stop iTunes from opening when a user presses one of the media keys. Thanks to
the Clementine project and tyler.s.rhodes@gmail.com for much of the code in this patch.
2011-05-31 18:17:46 -04:00
Christopher Reichert
30323c2d8a Shuffle Mode and Repeat mode are now stored on a playlist by playlist basis
in Tomahawk.conf
2011-05-31 10:43:45 -05:00
45 changed files with 1529 additions and 291 deletions

View File

@@ -5,6 +5,8 @@ SET( OS_SPECIFIC_LINK_LIBRARIES
${COREAUDIO_LIBRARY}
${COREFOUNDATION_LIBRARY}
SPMediaKeyTap
/System/Library/Frameworks/AppKit.framework
/System/Library/Frameworks/Carbon.framework
/System/Library/Frameworks/DiskArbitration.framework

View File

@@ -60,6 +60,7 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui}
sourcetree/items/genericpageitems.cpp
transferview.cpp
PipelineStatusView.cpp
tomahawktrayicon.cpp
audiocontrols.cpp
settingsdialog.cpp
@@ -69,6 +70,8 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui}
resolverconfigdelegate.cpp
resolversmodel.cpp
tomahawkwindow.cpp
win/qtwin.cpp
)
SET( tomahawkHeaders ${tomahawkHeaders}
@@ -102,6 +105,7 @@ SET( tomahawkHeadersGui ${tomahawkHeadersGui}
sourcetree/items/genericpageitems.h
transferview.h
PipelineStatusView.h
tomahawktrayicon.h
audiocontrols.h
settingsdialog.h
@@ -112,6 +116,9 @@ SET( tomahawkHeadersGui ${tomahawkHeadersGui}
resolversmodel.h
delegateconfigwrapper.h
tomahawkwindow.h
win/qtwin.h
)
SET( tomahawkUI ${tomahawkUI}
@@ -160,6 +167,8 @@ IF( UNIX )
ENDIF( UNIX )
IF( APPLE )
INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/thirdparty/SPMediaKeyTap )
SET( tomahawkHeaders ${tomahawkHeaders} mac/tomahawkapp_mac.h mac/macshortcuthandler.h )
SET( tomahawkSources ${tomahawkSources} mac/tomahawkapp_mac.mm mac/macshortcuthandler.cpp )

104
src/PipelineStatusView.cpp Normal file
View File

@@ -0,0 +1,104 @@
/* === 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 "PipelineStatusView.h"
#include <QHeaderView>
#include <QVBoxLayout>
#include "libtomahawk/pipeline.h"
using namespace Tomahawk;
PipelineStatusView::PipelineStatusView( AnimatedSplitter* parent )
: AnimatedWidget( parent )
, m_parent( parent )
{
setHiddenSize( QSize( 0, 0 ) );
setLayout( new QVBoxLayout() );
m_tree = new QTreeWidget( this );
layout()->setMargin( 0 );
layout()->addWidget( m_tree );
QStringList headers;
headers << tr( "Searching For" ) << tr( "Pending" );
m_tree->setHeaderLabels( headers );
m_tree->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored );
m_tree->setColumnCount( 2 );
m_tree->setColumnWidth( 0, 200 );
m_tree->setColumnWidth( 1, 50 );
m_tree->header()->setStretchLastSection( true );
m_tree->setRootIsDecorated( false );
m_tree->setFrameShape( QFrame::NoFrame );
m_tree->setAttribute( Qt::WA_MacShowFocusRect, 0 );
new QTreeWidgetItem( m_tree );
connect( Pipeline::instance(), SIGNAL( resolving( Tomahawk::query_ptr ) ), SLOT( onPipelineUpdate( Tomahawk::query_ptr ) ) );
connect( Pipeline::instance(), SIGNAL( idle() ), SLOT( onPipelineUpdate() ) );
onPipelineUpdate();
}
void
PipelineStatusView::onPipelineUpdate( const query_ptr& query )
{
qDebug() << Q_FUNC_INFO;
QTreeWidgetItem* ti = m_tree->invisibleRootItem()->child( 0 );
if ( Pipeline::instance()->activeQueryCount() && !query.isNull() )
{
ti->setText( 0, QString( "%1 - %2" ).arg( query->artist() ).arg( query->track() ) );
ti->setText( 1, QString( "%1" ).arg( Pipeline::instance()->activeQueryCount() + Pipeline::instance()->pendingQueryCount() ) );
if ( isHidden() )
emit showWidget();
}
else
{
ti->setText( 0, tr( "Idle" ) );
ti->setText( 1, QString( "None" ) );
if ( !isHidden() )
emit hideWidget();
}
}
QSize
PipelineStatusView::sizeHint() const
{
unsigned int y = 0;
y += m_tree->header()->height();
y += m_tree->contentsMargins().top() + m_tree->contentsMargins().bottom();
if ( m_tree->invisibleRootItem()->childCount() )
{
unsigned int rowheight = m_tree->sizeHintForRow( 0 );
y += rowheight * m_tree->invisibleRootItem()->childCount() + 2;
}
return QSize( 0, y );
}

View File

@@ -1,5 +1,5 @@
/* === 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
@@ -16,28 +16,36 @@
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#import <AppKit/NSApplication.h>
#ifndef PIPELINESTATUSVIEW_H
#define PIPELINESTATUSVIEW_H
#include "config.h"
#include <QDebug>
#include <QTreeWidget>
// this file copied and inspired by mac_startup.* in clementine player,
// copyright David Sansome 2010
namespace Tomahawk {
class PlatformInterface;
}
#include "typedefs.h"
#include "utils/animatedsplitter.h"
#ifdef SNOW_LEOPARD
@interface AppDelegate : NSObject <NSApplicationDelegate> {
#else
@interface AppDelegate : NSObject {
#endif
Tomahawk::PlatformInterface* application_handler_;
//NSMenu* dock_menu_;
}
class StreamConnection;
- (id) initWithHandler: (Tomahawk::PlatformInterface*)handler;
// NSApplicationDelegate
- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag;
//- (NSMenu*) applicationDockMenu: (NSApplication*)sender;
//- (void) setDockMenu: (NSMenu*)menu;
@end
class PipelineStatusView : public AnimatedWidget
{
Q_OBJECT
public:
explicit PipelineStatusView( AnimatedSplitter* parent );
virtual ~PipelineStatusView()
{
qDebug() << Q_FUNC_INFO;
}
QSize sizeHint() const;
private slots:
void onPipelineUpdate( const Tomahawk::query_ptr& query = Tomahawk::query_ptr() );
private:
QTreeWidget* m_tree;
AnimatedSplitter* m_parent;
};
#endif // TRANSFERVIEW_H

View File

@@ -80,27 +80,23 @@ AudioControls::AudioControls( QWidget* parent )
ui->metaDataArea->setStyleSheet( "QWidget#metaDataArea {\nborder-width: 4px;\nborder-image: url(" RESPATH "images/now-playing-panel.png) 4 4 4 4 stretch stretch; }" );
ui->seekSlider->setFixedHeight( 20 );
ui->seekSlider->setEnabled( false );
ui->seekSlider->setEnabled( true );
ui->seekSlider->setStyleSheet( "QSlider::groove::horizontal {"
"margin: 5px; border-width: 3px;"
"border-image: url(" RESPATH "images/seek-slider-bkg.png) 3 3 3 3 stretch stretch;"
"}"
"QSlider::handle::horizontal {"
"margin-left: 5px; margin-right: -5px; "
"width: 0px;"
//"margin-bottom: -7px; margin-top: -7px;"
//"height: 17px; width: 16px;"
//"background-image: url(" RESPATH "images/seek-and-volume-knob-rest.png);"
//"background-repeat: no-repeat;"
"}"
"QSlider::sub-page:horizontal {"
"margin: 5px; border-width: 3px;"
"border-image: url(" RESPATH "images/seek-slider-level.png) 3 3 3 3 stretch stretch;"
"}"
);
"QSlider::handle::horizontal {"
"margin-bottom: -7px; margin-top: -7px;"
"height: 17px; width: 16px;"
"background-image: url(" RESPATH "images/seek-and-volume-knob-rest.png);"
"background-repeat: no-repeat;"
"}" );
ui->volumeSlider->setFixedHeight( 20 );
ui->volumeSlider->setRange( 0, 100 );
@@ -120,9 +116,7 @@ AudioControls::AudioControls( QWidget* parent )
"height: 17px; width: 16px;"
"background-image: url(" RESPATH "images/seek-and-volume-knob-rest.png);"
"background-repeat: no-repeat;"
"}"
);
"}" );
/* m_playAction = new QAction( this );
m_pauseAction = new QAction( this );
@@ -134,6 +128,7 @@ AudioControls::AudioControls( QWidget* parent )
connect( m_prevAction, SIGNAL( triggered() ), (QObject*)APP->audioEngine(), SLOT( previous() ) );
connect( m_nextAction, SIGNAL( triggered() ), (QObject*)APP->audioEngine(), SLOT( next() ) ); */
connect( ui->seekSlider, SIGNAL( valueChanged( int ) ), AudioEngine::instance(), SLOT( seek( int ) ) );
connect( ui->volumeSlider, SIGNAL( valueChanged( int ) ), AudioEngine::instance(), SLOT( setVolume( int ) ) );
connect( ui->prevButton, SIGNAL( clicked() ), AudioEngine::instance(), SLOT( previous() ) );
connect( ui->playPauseButton, SIGNAL( clicked() ), AudioEngine::instance(), SLOT( play() ) );
@@ -283,13 +278,11 @@ AudioControls::onPlaybackLoading( const Tomahawk::result_ptr& result )
ui->ownerLabel->setText( result->friendlySource() );
ui->coverImage->setPixmap( m_defaultCover );
if ( ui->timeLabel->text().isEmpty() )
ui->timeLabel->setText( TomahawkUtils::timeToString( 0 ) );
if ( ui->timeLeftLabel->text().isEmpty() )
ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( result->duration() ) );
ui->timeLabel->setText( TomahawkUtils::timeToString( 0 ) );
ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( result->duration() ) );
ui->seekSlider->setRange( 0, m_currentTrack->duration() * 1000 );
ui->seekSlider->setValue( 0 );
ui->seekSlider->setVisible( true );
/* m_playAction->setEnabled( false );
@@ -357,10 +350,14 @@ AudioControls::onPlaybackTimer( qint64 msElapsed )
if ( m_currentTrack.isNull() )
return;
ui->seekSlider->blockSignals( true );
const int seconds = msElapsed / 1000;
ui->timeLabel->setText( TomahawkUtils::timeToString( seconds ) );
ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( m_currentTrack->duration() - seconds ) );
ui->seekSlider->setValue( msElapsed );
ui->seekSlider->blockSignals( false );
}

View File

@@ -136,6 +136,17 @@ AudioEngine::next()
}
void
AudioEngine::seek( int ms )
{
if ( isPlaying() || isPaused() )
{
qDebug() << Q_FUNC_INFO << ms;
m_mediaObject->seek( ms );
}
}
void
AudioEngine::setVolume( int percentage )
{
@@ -176,7 +187,7 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result )
{
setCurrentTrack( result );
if ( !isHttpResult( m_currentTrack->url() ) )
if ( !isHttpResult( m_currentTrack->url() ) && !isLocalResult( m_currentTrack->url() ) )
{
io = Servent::instance()->getIODeviceForUrl( m_currentTrack );
@@ -204,7 +215,7 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result )
m_expectStop = true;
}
if ( !isHttpResult( m_currentTrack->url() ) )
if ( !isHttpResult( m_currentTrack->url() ) && !isLocalResult( m_currentTrack->url() ) )
{
m_mediaObject->setCurrentSource( io.data() );
m_mediaObject->currentSource().setAutoDelete( false );
@@ -217,7 +228,6 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result )
{
furl = QUrl( m_currentTrack->url().left( m_currentTrack->url().indexOf( '?' ) ) );
furl.setEncodedQuery( QString( m_currentTrack->url().mid( m_currentTrack->url().indexOf( '?' ) + 1 ) ).toLocal8Bit() );
qDebug() << Q_FUNC_INFO << furl;
}
m_mediaObject->setCurrentSource( furl );
m_mediaObject->currentSource().setAutoDelete( true );
@@ -306,11 +316,22 @@ AudioEngine::onStateChanged( Phonon::State newState, Phonon::State oldState )
{
qDebug() << "Phonon Error:" << m_mediaObject->errorString() << m_mediaObject->errorType();
}
if ( oldState == Phonon::PlayingState && newState == Phonon::StoppedState )
{
qDebug() << "Expecting stop?" << m_expectStop;
if ( !m_expectStop )
{
qDebug() << "Loading next track.";
m_expectStop = false;
loadNextTrack();
}
}
else if ( oldState == Phonon::PlayingState && newState == Phonon::PausedState )
{
qDebug() << m_mediaObject->currentTime() << m_mediaObject->totalTime();
if ( m_mediaObject->currentTime() == m_mediaObject->totalTime() )
{
qDebug() << "Loading next track.";
m_expectStop = false;
loadNextTrack();
}
@@ -365,8 +386,16 @@ AudioEngine::setCurrentTrack( const Tomahawk::result_ptr& result )
m_currentTrack = result;
}
bool
AudioEngine::isHttpResult( const QString& url ) const
{
return url.startsWith( "http://" );
}
bool
AudioEngine::isLocalResult( const QString& url ) const
{
return url.startsWith( "file://" );
}

View File

@@ -64,6 +64,7 @@ public slots:
void previous();
void next();
void seek( int ms );
void setVolume( int percentage );
void lowerVolume() { setVolume( volume() - AUDIO_VOLUME_STEP ); }
void raiseVolume() { setVolume( volume() + AUDIO_VOLUME_STEP ); }
@@ -106,6 +107,7 @@ private slots:
private:
bool isHttpResult( const QString& ) const;
bool isLocalResult( const QString& ) const;
bool m_isPlayingHttp;
QSharedPointer<QIODevice> m_input;

View File

@@ -85,14 +85,14 @@ GlobalActionManager::openLinkFromQuery( const Tomahawk::query_ptr& query ) const
return link;
}
void
QString
GlobalActionManager::copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& playlist )
{
QUrl link( QString( "tomahawk://%1/create/" ).arg( playlist->mode() == Tomahawk::OnDemand ? "station" : "autoplaylist" ) );
if( playlist->generator()->type() != "echonest" ) {
qDebug() << "Only echonest generators are supported";
return;
return QString();
}
link.addEncodedQueryItem( "type", "echonest" );
@@ -123,6 +123,8 @@ GlobalActionManager::copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& p
QClipboard* cb = QApplication::clipboard();
cb->setText( link.toEncoded() );
return link.toString();
}
void
@@ -380,22 +382,22 @@ GlobalActionManager::handleSearchCommand( const QUrl& url )
bool
GlobalActionManager::handleAutoPlaylistCommand( const QUrl& url )
{
return loadDynamicPlaylist( url, false );
return !loadDynamicPlaylist( url, false ).isNull();
}
bool
Tomahawk::dynplaylist_ptr
GlobalActionManager::loadDynamicPlaylist( const QUrl& url, bool station )
{
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) {
qDebug() << "No specific station command:" << url.toString();
return false;
return Tomahawk::dynplaylist_ptr();
}
if( parts[ 0 ] == "create" ) {
if( !url.hasQueryItem( "title" ) || !url.hasQueryItem( "type" ) ) {
qDebug() << "Station create command needs title and type..." << url.toString();
return false;
return Tomahawk::dynplaylist_ptr();
}
QString title = url.queryItemValue( "title" );
QString type = url.queryItemValue( "type" );
@@ -520,17 +522,17 @@ GlobalActionManager::loadDynamicPlaylist( const QUrl& url, bool station )
else
pl->createNewRevision( uuid(), pl->currentrevision(), type, controls, pl->entries() );
return true;
return pl;
}
return false;
return Tomahawk::dynplaylist_ptr();
}
bool
GlobalActionManager::handleStationCommand( const QUrl& url )
{
return loadDynamicPlaylist( url, true );
return !loadDynamicPlaylist( url, true ).isNull();
}
bool

View File

@@ -38,13 +38,14 @@ public:
QUrl openLinkFromQuery( const Tomahawk::query_ptr& query ) const;
void copyToClipboard( const Tomahawk::query_ptr& query ) const;
void copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& playlist );
QString copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& playlist );
void savePlaylistToFile( const Tomahawk::playlist_ptr& playlist, const QString& filename );
public slots:
bool parseTomahawkLink( const QString& link );
void waitingForResolved( bool );
Tomahawk::dynplaylist_ptr loadDynamicPlaylist( const QUrl& url, bool station );
private slots:
void bookmarkPlaylistCreated( const Tomahawk::playlist_ptr& pl );
void showPlaylist();
@@ -65,7 +66,6 @@ private:
bool handleBookmarkCommand(const QUrl& url );
bool handleOpenCommand(const QUrl& url );
bool loadDynamicPlaylist( const QUrl& url, bool station );
bool doQueueAdd( const QStringList& parts, const QList< QPair< QString, QString > >& queryItems );
Tomahawk::playlist_ptr m_toShow;

View File

@@ -311,6 +311,7 @@ Pipeline::shunt( const query_ptr& q )
qDebug() << "Dispatching to resolver" << r->name() << q->toString() << q->solved() << q->id();
r->resolve( q );
emit resolving( q );
}
else
break;

View File

@@ -46,6 +46,9 @@ public:
explicit Pipeline( QObject* parent = 0 );
virtual ~Pipeline();
unsigned int pendingQueryCount() const { return m_queries_pending.count(); }
unsigned int activeQueryCount() const { return m_qidsState.count(); }
void reportResults( QID qid, const QList< result_ptr >& results );
/// sorter to rank resolver priority
@@ -75,6 +78,7 @@ public slots:
signals:
void idle();
void resolving( const Tomahawk::query_ptr& query );
private slots:
void timeoutShunt( const query_ptr& q );

View File

@@ -28,6 +28,7 @@
#include "database/databasecommand_deleteplaylist.h"
#include "database/databasecommand_renameplaylist.h"
#include "tomahawksettings.h"
#include "pipeline.h"
#include "source.h"
#include "sourcelist.h"
@@ -219,6 +220,9 @@ Playlist::load( const QString& guid )
bool
Playlist::remove( const playlist_ptr& playlist )
{
TomahawkSettings *s = TomahawkSettings::instance();
s->removePlaylistSettings( playlist->guid() );
DatabaseCommand_DeletePlaylist* cmd = new DatabaseCommand_DeletePlaylist( playlist->author(), playlist->guid() );
Database::instance()->enqueue( QSharedPointer<DatabaseCommand>(cmd) );

View File

@@ -99,6 +99,7 @@ PlaylistView::setupMenus()
foreach( QAction* a, actions() )
m_itemMenu.addAction( a );
// m_addItemsToPlaylistAction = m_itemMenu.addAction( tr( "&Add to Playlist" ) );
// m_itemMenu.addSeparator();
m_deleteItemsAction = m_itemMenu.addAction( i > 1 ? tr( "&Delete Items" ) : tr( "&Delete Item" ) );

View File

@@ -1,5 +1,5 @@
/* === 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
@@ -45,7 +45,7 @@ QueueView::QueueView( AnimatedSplitter* parent )
m_queue->setFrameShape( QFrame::NoFrame );
m_queue->setAttribute( Qt::WA_MacShowFocusRect, 0 );
m_queue->overlay()->setEnabled( false );
m_button = new QPushButton();
m_button->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
m_button->setText( tr( "Click to show queue" ) );
@@ -64,13 +64,13 @@ QueueView::~QueueView()
void
QueueView::onShown( QWidget* widget )
QueueView::onShown( QWidget* widget, bool animated )
{
qDebug() << Q_FUNC_INFO << widget;
if ( widget != this )
return;
AnimatedWidget::onShown( widget );
AnimatedWidget::onShown( widget, animated );
m_button->setText( tr( "Click to hide queue" ) );
disconnect( m_button, SIGNAL( clicked() ), this, SIGNAL( showWidget() ) );
@@ -79,14 +79,14 @@ QueueView::onShown( QWidget* widget )
void
QueueView::onHidden( QWidget* widget )
QueueView::onHidden( QWidget* widget, bool animated )
{
qDebug() << Q_FUNC_INFO << widget;
if ( widget != this )
return;
AnimatedWidget::onHidden( widget );
AnimatedWidget::onHidden( widget, animated );
m_button->setText( tr( "Click to show queue" ) );
disconnect( m_button, SIGNAL( clicked() ), this, SIGNAL( hideWidget() ) );
connect( m_button, SIGNAL( clicked() ), SIGNAL( showWidget() ) );

View File

@@ -1,5 +1,5 @@
/* === 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
@@ -39,8 +39,8 @@ public:
QSize sizeHint() const { return QSize( 0, 200 ); }
public slots:
virtual void onShown( QWidget* );
virtual void onHidden( QWidget* );
virtual void onShown( QWidget*, bool animated );
virtual void onHidden( QWidget*, bool animated );
private:
PlaylistView* m_queue;

View File

@@ -26,6 +26,7 @@
#include <QDir>
#include <QDebug>
#include "sip/SipHandler.h"
#include "playlistinterface.h"
#define VERSION 3
@@ -187,7 +188,6 @@ TomahawkSettings::setWatchForChanges( bool watch )
setValue( "watchForChanges", watch );
}
void
TomahawkSettings::setAcceptedLegalWarning( bool accept )
{
@@ -381,6 +381,36 @@ TomahawkSettings::setPlaylistColumnSizes( const QString& playlistid, const QByte
setValue( QString( "ui/playlist/%1/columnSizes" ).arg( playlistid ), state );
}
bool
TomahawkSettings::shuffleState( const QString& playlistid ) const
{
return value( QString( "ui/playlist/%1/shuffleState" ).arg( playlistid )).toBool();
}
void
TomahawkSettings::setShuffleState( const QString& playlistid, bool state)
{
setValue( QString( "ui/playlist/%1/shuffleState" ).arg( playlistid ), state );
}
void
TomahawkSettings::removePlaylistSettings( const QString& playlistid )
{
remove( QString( "ui/playlist/%1/shuffleState" ).arg( playlistid ) );
remove( QString( "ui/playlist/%1/repeatMode" ).arg( playlistid ) );
}
void
TomahawkSettings::setRepeatMode( const QString& playlistid, PlaylistInterface::RepeatMode mode )
{
setValue( QString( "ui/playlist/%1/repeatMode" ).arg( playlistid ), (int)mode );
}
PlaylistInterface::RepeatMode
TomahawkSettings::repeatMode( const QString& playlistid )
{
return (PlaylistInterface::RepeatMode)value( QString( "ui/playlist/%1/repeatMode" ).arg( playlistid )).toInt();
}
QList<Tomahawk::playlist_ptr>
TomahawkSettings::recentlyPlayedPlaylists() const

View File

@@ -68,6 +68,14 @@ public:
QList<Tomahawk::playlist_ptr> recentlyPlayedPlaylists() const;
QStringList recentlyPlayedPlaylistGuids( unsigned int amount = 0 ) const;
void appendRecentlyPlayedPlaylist( const Tomahawk::playlist_ptr& playlist );
bool shuffleState( const QString& playlistid ) const;
void setShuffleState( const QString& playlistid, bool state );
PlaylistInterface::RepeatMode repeatMode( const QString& playlistid );
void setRepeatMode( const QString& playlistid, PlaylistInterface::RepeatMode mode);
// remove shuffle state and repeat state
void removePlaylistSettings( const QString& playlistid );
/// SIP plugins
// all plugins we know about. loaded, unloaded, enabled, disabled.

View File

@@ -1,5 +1,5 @@
/* === 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
@@ -23,83 +23,25 @@
AnimatedSplitter::AnimatedSplitter( QWidget* parent )
: QSplitter( parent )
, m_animateIndex( -1 )
, m_greedyIndex( 0 )
{
setHandleWidth( 1 );
m_timeLine = new QTimeLine( ANIMATION_TIME, this );
m_timeLine->setUpdateInterval( 5 );
m_timeLine->setEasingCurve( QEasingCurve::OutBack );
connect( m_timeLine, SIGNAL( frameChanged( int ) ), SLOT( onAnimationStep( int ) ) );
connect( m_timeLine, SIGNAL( finished() ), SLOT( onAnimationFinished() ) );
}
void
AnimatedSplitter::show( int index, bool animate )
{
m_animateIndex = index;
QWidget* w = widget( index );
QSize size = w->sizeHint();
if ( w->height() == size.height() )
return;
emit shown( w );
w->setMaximumHeight( QWIDGETSIZE_MAX );
qDebug() << "animating to:" << size.height() << "from" << w->height();
m_animateForward = true;
if ( animate )
{
if ( m_timeLine->state() == QTimeLine::Running )
m_timeLine->stop();
m_timeLine->setFrameRange( w->height(), size.height() );
m_timeLine->setDirection( QTimeLine::Forward );
m_timeLine->start();
}
else
{
onAnimationStep( size.height() );
onAnimationFinished();
}
emit shown( w, animate );
}
void
AnimatedSplitter::hide( int index, bool animate )
{
m_animateIndex = index;
QWidget* w = widget( index );
int minHeight = m_sizes.at( index ).height();
if ( w->height() == minHeight )
return;
emit hidden( w );
w->setMinimumHeight( minHeight );
// qDebug() << "animating to:" << w->height() << "from" << minHeight;
m_animateForward = false;
if ( animate )
{
if ( m_timeLine->state() == QTimeLine::Running )
m_timeLine->stop();
m_timeLine->setFrameRange( minHeight, w->height() );
m_timeLine->setDirection( QTimeLine::Backward );
m_timeLine->start();
}
else
{
onAnimationStep( minHeight );
onAnimationFinished();
}
emit hidden( w, animate );
}
@@ -107,7 +49,6 @@ void
AnimatedSplitter::addWidget( QWidget* widget )
{
QSplitter::addWidget( widget );
m_sizes << widget->minimumSize();
}
@@ -116,13 +57,11 @@ AnimatedSplitter::addWidget( AnimatedWidget* widget )
{
qDebug() << Q_FUNC_INFO << widget;
QSplitter::addWidget( widget );
m_sizes << widget->hiddenSize();
connect( widget, SIGNAL( showWidget() ), SLOT( onShowRequest() ) );
connect( widget, SIGNAL( hideWidget() ), SLOT( onHideRequest() ) );
connect( widget, SIGNAL( hiddenSizeChanged() ), SLOT( onHiddenSizeChanged() ) );
connect( this, SIGNAL( shown( QWidget* ) ), widget, SLOT( onShown( QWidget* ) ) );
connect( this, SIGNAL( hidden( QWidget* ) ), widget, SLOT( onHidden( QWidget* ) ) );
connect( this, SIGNAL( shown( QWidget*, bool ) ), widget, SLOT( onShown( QWidget*, bool ) ) );
connect( this, SIGNAL( hidden( QWidget*, bool ) ), widget, SLOT( onHidden( QWidget*, bool ) ) );
}
@@ -131,18 +70,9 @@ AnimatedSplitter::onShowRequest()
{
qDebug() << Q_FUNC_INFO << sender();
int j = -1;
for ( int i = 0; i < count(); i ++ )
{
if ( widget( i ) == sender() )
{
j = i;
break;
}
}
if ( j > 0 )
show( j );
AnimatedWidget* w = (AnimatedWidget*)(sender());
if ( indexOf( w ) > 0 )
show( indexOf( w ) );
else
qDebug() << "Could not find widget:" << sender();
}
@@ -151,72 +81,16 @@ AnimatedSplitter::onShowRequest()
void
AnimatedSplitter::onHideRequest()
{
int j = -1;
for ( int i = 0; i < count(); i ++ )
{
if ( widget( i ) == sender() )
{
j = i;
break;
}
}
if ( j > 0 )
hide( j );
AnimatedWidget* w = (AnimatedWidget*)(sender());
if ( indexOf( w ) > 0 )
hide( indexOf( w ) );
else
qDebug() << "Could not find widget:" << sender();
}
void
AnimatedSplitter::onAnimationStep( int frame )
{
QList< int > sizes;
for ( int i = 0; i < count(); i ++ )
{
int j = 0;
if ( i == m_greedyIndex )
{
j = height() - frame; // FIXME
}
else if ( i == m_animateIndex )
{
j = frame;
}
else
{
j = widget( i )->height();
}
sizes << j;
}
setSizes( sizes );
}
void
AnimatedSplitter::onAnimationFinished()
{
qDebug() << Q_FUNC_INFO;
QWidget* w = widget( m_animateIndex );
if ( m_animateForward )
{
w->setMinimumHeight( w->minimumHeight() );
}
else
{
w->setMaximumHeight( m_sizes.at( m_animateIndex ).height() );
}
m_animateIndex = -1;
}
void
AnimatedSplitter::setGreedyWidget(int index)
AnimatedSplitter::setGreedyWidget( int index )
{
m_greedyIndex = index;
if( !widget( index ) )
@@ -227,17 +101,7 @@ AnimatedSplitter::setGreedyWidget(int index)
else
policy.setVerticalStretch( 1 );
widget( m_greedyIndex )->setSizePolicy( policy );
}
void
AnimatedSplitter::onHiddenSizeChanged()
{
AnimatedWidget* w = (AnimatedWidget*)(sender());
int i = indexOf( w );
m_sizes.replace( i, w->hiddenSize() );
}
@@ -246,24 +110,98 @@ AnimatedWidget::AnimatedWidget( AnimatedSplitter* parent )
, m_isHidden( false )
{
qDebug() << Q_FUNC_INFO;
m_timeLine = new QTimeLine( ANIMATION_TIME, this );
m_timeLine->setUpdateInterval( 5 );
m_timeLine->setEasingCurve( QEasingCurve::OutBack );
connect( m_timeLine, SIGNAL( frameChanged( int ) ), SLOT( onAnimationStep( int ) ) );
connect( m_timeLine, SIGNAL( finished() ), SLOT( onAnimationFinished() ) );
}
AnimatedWidget::~AnimatedWidget()
{
}
void
AnimatedWidget::onShown( QWidget* )
AnimatedWidget::onShown( QWidget* widget, bool animated )
{
if ( widget != this )
return;
qDebug() << Q_FUNC_INFO << this;
m_animateForward = true;
if ( animated )
{
if ( m_timeLine->state() == QTimeLine::Running )
m_timeLine->stop();
m_timeLine->setFrameRange( height(), sizeHint().height() );
m_timeLine->setDirection( QTimeLine::Forward );
m_timeLine->start();
}
else
{
onAnimationStep( sizeHint().height() );
onAnimationFinished();
}
m_isHidden = false;
}
void
AnimatedWidget::onHidden( QWidget* )
AnimatedWidget::onHidden( QWidget* widget, bool animated )
{
if ( widget != this )
return;
qDebug() << Q_FUNC_INFO << this;
m_animateForward = false;
int minHeight = hiddenSize().height();
if ( animated )
{
if ( m_timeLine->state() == QTimeLine::Running )
m_timeLine->stop();
m_timeLine->setFrameRange( minHeight, height() );
m_timeLine->setDirection( QTimeLine::Backward );
m_timeLine->start();
}
else
{
onAnimationStep( minHeight );
onAnimationFinished();
}
m_isHidden = true;
}
void
AnimatedWidget::onAnimationStep( int frame )
{
setFixedHeight( frame );
}
void
AnimatedWidget::onAnimationFinished()
{
qDebug() << Q_FUNC_INFO;
if ( m_animateForward )
{
setMinimumHeight( hiddenSize().height() );
setMaximumHeight( QWIDGETSIZE_MAX );
}
else
{
setFixedHeight( hiddenSize().height() );
}
}

View File

@@ -1,5 +1,5 @@
/* === 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
@@ -43,56 +43,52 @@ public:
void addWidget( AnimatedWidget* widget );
signals:
void shown( QWidget* );
void hidden( QWidget* );
void shown( QWidget*, bool animated );
void hidden( QWidget*, bool animated );
private slots:
void onShowRequest();
void onHideRequest();
void onAnimationStep( int frame );
void onAnimationFinished();
void onHiddenSizeChanged();
private:
int m_animateIndex;
bool m_animateForward;
int m_greedyIndex;
QList<QSize> m_sizes;
QTimeLine* m_timeLine;
};
class DLLEXPORT AnimatedWidget : public QWidget
{
Q_OBJECT
public:
explicit AnimatedWidget( AnimatedSplitter* parent = 0 );
explicit AnimatedWidget( AnimatedSplitter* parent );
virtual ~AnimatedWidget();
QSize hiddenSize() const { return m_hiddenSize; }
void setHiddenSize( const QSize& size ) { m_hiddenSize = size; emit hiddenSizeChanged(); }
bool isHidden() const { return m_isHidden; }
public slots:
virtual void onShown( QWidget* );
virtual void onHidden( QWidget* );
virtual void onShown( QWidget*, bool animated );
virtual void onHidden( QWidget*, bool animated );
signals:
void showWidget();
void hideWidget();
void hiddenSizeChanged();
private slots:
void onAnimationStep( int frame );
void onAnimationFinished();
protected:
AnimatedSplitter* splitter() { return m_parent; }
private:
AnimatedSplitter* m_parent;
bool m_animateForward;
QSize m_hiddenSize;
bool m_isHidden;
QTimeLine* m_timeLine;
};
#endif //ANIMATEDSPLITTER_H

View File

@@ -142,6 +142,7 @@ ViewManager::ViewManager( QObject* parent )
ViewManager::~ViewManager()
{
saveCurrentPlaylistSettings();
delete m_widget;
}
@@ -581,6 +582,9 @@ ViewManager::setPage( ViewPage* page, bool trackHistory )
if ( !page )
return;
// save the old playlist shuffle state in config before we change playlists
saveCurrentPlaylistSettings();
unlinkPlaylist();
if ( !m_pageHistory.contains( page ) )
@@ -652,6 +656,24 @@ ViewManager::unlinkPlaylist()
}
}
void
ViewManager::saveCurrentPlaylistSettings()
{
TomahawkSettings* s = TomahawkSettings::instance();
Tomahawk::playlist_ptr pl = playlistForInterface( currentPlaylistInterface() );
if ( !pl.isNull() ) {
s->setShuffleState( pl->guid(), currentPlaylistInterface()->shuffled() );
s->setRepeatMode( pl->guid(), currentPlaylistInterface()->repeatMode() );
} else {
Tomahawk::dynplaylist_ptr dynPl = dynamicPlaylistForInterface( currentPlaylistInterface() );
if ( !dynPl.isNull() ) {
s->setShuffleState( dynPl->guid(), currentPlaylistInterface()->shuffled() );
s->setRepeatMode( dynPl->guid(), currentPlaylistInterface()->repeatMode() );
}
}
}
void
ViewManager::updateView()
@@ -704,8 +726,26 @@ ViewManager::updateView()
m_infobar->setCaption( currentPage()->title() );
m_infobar->setDescription( currentPage()->description() );
m_infobar->setPixmap( currentPage()->pixmap() );
// turn on shuffle/repeat mode for the new playlist view if specified in config
loadCurrentPlaylistSettings();
}
void
ViewManager::loadCurrentPlaylistSettings()
{
TomahawkSettings* s = TomahawkSettings::instance();
Tomahawk::playlist_ptr pl = playlistForInterface( currentPlaylistInterface() );
if ( !pl.isNull() ) {
currentPlaylistInterface()->setShuffled( s->shuffleState( pl->guid() ));
currentPlaylistInterface()->setRepeatMode( s->repeatMode( pl->guid() ));
} else {
Tomahawk::dynplaylist_ptr dynPl = dynamicPlaylistForInterface( currentPlaylistInterface() );
if ( !dynPl.isNull() ) {
currentPlaylistInterface()->setShuffled( s->shuffleState( dynPl->guid() ));
}
}
}
void
ViewManager::onWidgetDestroyed( QWidget* widget )

View File

@@ -162,7 +162,9 @@ private:
void setPage( Tomahawk::ViewPage* page, bool trackHistory = true );
void updateView();
void unlinkPlaylist();
void saveCurrentPlaylistSettings();
void loadCurrentPlaylistSettings();
Tomahawk::playlist_ptr playlistForInterface( PlaylistInterface* interface ) const;
Tomahawk::dynplaylist_ptr dynamicPlaylistForInterface( PlaylistInterface* interface ) const;
Tomahawk::collection_ptr collectionForInterface( PlaylistInterface* interface ) const;

40
src/mac/macdelegate.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef MACDELEGATE_H
#define MACDELEGATE_H
// This file inspired by clementine's macdelegate.h
#import <AppKit/NSApplication.h>
#include "SPMediaKeyTap.h"
namespace Tomahawk {
class MacShortcutHandler;
class PlatformInterface;
}
#ifdef SNOW_LEOPARD
@interface AppDelegate :NSObject <NSApplicationDelegate> {
#else
@interface AppDelegate :NSObject {
#endif
Tomahawk::PlatformInterface* application_handler_;
NSMenu* dock_menu_;
SPMediaKeyTap* key_tap_;
Tomahawk::MacShortcutHandler* shortcut_handler_;
}
- (id) initWithHandler: (Tomahawk::PlatformInterface*)handler;
// NSApplicationDelegate
- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag;
- (NSMenu*) applicationDockMenu: (NSApplication*)sender;
- (void) setDockMenu: (NSMenu*)menu;
- (Tomahawk::MacShortcutHandler*) shortcutHandler;
- (void) setShortcutHandler: (Tomahawk::MacShortcutHandler*)backend;
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification;
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*)sender;
- (void) mediaKeyTap: (SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
@end
#endif // MACDELEGATE_H

View File

@@ -17,7 +17,7 @@
*/
#include "tomahawkapp_mac.h"
#include "tomahawkapp_macdelegate.h"
#include "macdelegate.h"
#include "macshortcuthandler.h"
#include <QDebug>
@@ -42,6 +42,7 @@
// See: http://www.rogueamoeba.com/utm/2007/09/29/apple-keyboard-media-key-event-handling/
@interface MacApplication :NSApplication {
AppDelegate* delegate_;
Tomahawk::MacShortcutHandler* shortcut_handler_;
Tomahawk::PlatformInterface* application_handler_;
}
@@ -51,7 +52,6 @@
- (Tomahawk::PlatformInterface*) application_handler;
- (void) setApplicationHandler: (Tomahawk::PlatformInterface*)handler;
- (void) mediaKeyEvent: (int)key state: (BOOL)state repeat: (BOOL)repeat;
@end
@@ -59,14 +59,22 @@
- (id) init {
if ((self = [super init])) {
application_handler_ = nil;
// dock_menu_ = nil;
application_handler_ = nil;
shortcut_handler_ = nil;
//dock_menu_ = nil;
}
return self;
}
- (id) initWithHandler: (Tomahawk::PlatformInterface*)handler {
application_handler_ = handler;
// Register defaults for the whitelist of apps that want to use media keys
[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
[SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers], @"SPApplicationsNeedingMediaKeys",
nil]];
return self;
}
@@ -76,7 +84,7 @@
}
return YES;
}
/*
- (void) setDockMenu: (NSMenu*)menu {
dock_menu_ = menu;
}
@@ -84,7 +92,45 @@
- (NSMenu*) applicationDockMenu: (NSApplication*)sender {
return dock_menu_;
}
*/
- (Tomahawk::MacShortcutHandler*) shortcutHandler {
return shortcut_handler_;
}
- (void) setShortcutHandler: (Tomahawk::MacShortcutHandler*)handler {
qDebug() << "Setting shortcut handler of MacApp";
// should be the same as MacApplication's
shortcut_handler_ = handler;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
key_tap_ = [[SPMediaKeyTap alloc] initWithDelegate:self];
if([SPMediaKeyTap usesGlobalMediaKeyTap])
[key_tap_ startWatchingMediaKeys];
else
qWarning()<<"Media key monitoring disabled";
}
- (void) mediaKeyTap: (SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event {
NSAssert([event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys, @"Unexpected NSEvent in mediaKeyTap:receivedMediaKeyEvent:");
int key_code = (([event data1] & 0xFFFF0000) >> 16);
int key_flags = ([event data1] & 0x0000FFFF);
BOOL key_is_pressed = (((key_flags & 0xFF00) >> 8)) == 0xA;
// not used. keep just in case
// int key_repeat = (key_flags & 0x1);
if (!shortcut_handler_) {
qWarning() << "No shortcut handler when we get a media key event...";
return;
}
if (key_is_pressed) {
shortcut_handler_->macMediaKeyPressed(key_code);
}
}
- (BOOL) application: (NSApplication*)app openFile:(NSString*)filename {
qDebug() << "Wants to open:" << [filename UTF8String];
@@ -94,6 +140,11 @@
return NO;
}
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*) sender {
return NSTerminateNow;
}
@end
@implementation MacApplication
@@ -127,7 +178,7 @@
}
- (void) setShortcutHandler: (Tomahawk::MacShortcutHandler*)handler {
qDebug() << "Setting shortcut handler of MacAPp";
// should be the same as AppDelegate's
shortcut_handler_ = handler;
}
@@ -136,30 +187,22 @@
}
- (void) setApplicationHandler: (Tomahawk::PlatformInterface*)handler {
AppDelegate* delegate = [[AppDelegate alloc] initWithHandler:handler];
[self setDelegate:delegate];
delegate_ = [[AppDelegate alloc] initWithHandler:handler];
// App-shortcut-handler set before delegate is set.
// this makes sure the delegate's shortcut_handler is set
[delegate_ setShortcutHandler:shortcut_handler_];
[self setDelegate:delegate_];
}
-(void) sendEvent: (NSEvent*)event {
if ([event type] == NSSystemDefined && [event subtype] == 8) {
int keycode = (([event data1] & 0xFFFF0000) >> 16);
int keyflags = ([event data1] & 0x0000FFFF);
int keystate = (((keyflags & 0xFF00) >> 8)) == 0xA;
int keyrepeat = (keyflags & 0x1);
// If event tap is not installed, handle events that reach the app instead
BOOL shouldHandleMediaKeyEventLocally = ![SPMediaKeyTap usesGlobalMediaKeyTap];
[self mediaKeyEvent: keycode state: keystate repeat: keyrepeat];
}
if(shouldHandleMediaKeyEventLocally && [event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys) {
[(id)[self delegate] mediaKeyTap: nil receivedMediaKeyEvent: event];
}
[super sendEvent: event];
}
-(void) mediaKeyEvent: (int)key state: (BOOL)state repeat: (BOOL)repeat {
if (!shortcut_handler_) {
return;
}
if (state == 0) {
shortcut_handler_->macMediaKeyPressed(key);
}
[super sendEvent: event];
}
@end

View File

@@ -19,6 +19,7 @@
#include "tomahawkapp.h"
#include "kdsingleapplicationguard/kdsingleapplicationguard.h"
#include "win/qtwin.h"
#include <QTranslator>
@@ -61,6 +62,12 @@ main( int argc, char *argv[] )
a.loadUrl( arg );
}
// make translucent on windows7
if ( QtWin::isCompositionEnabled() )
{
QtWin::extendFrameIntoClientArea( a.mainWindow() );
a.mainWindow()->setContentsMargins( 0, 0, 0, 0 );
}
return a.exec();
}

View File

@@ -382,7 +382,7 @@ SettingsDialog::onLastFmFinished()
ui->pushButtonTestLastfmLogin->setText( tr( "Failed" ) );
ui->pushButtonTestLastfmLogin->setEnabled( true );
break;
default:
qDebug() << "Couldn't get last.fm auth result";
ui->pushButtonTestLastfmLogin->setText( tr( "Could not contact server" ) );
@@ -528,6 +528,15 @@ SettingsDialog::sipFactoryClicked( SipPluginFactory* factory )
SipPlugin* p = factory->createPlugin();
bool added = false;
if( p->configWidget() ) {
#ifdef Q_OS_MAC
// on osx a sheet needs to be non-modal
DelegateConfigWrapper* dialog = new DelegateConfigWrapper( p->configWidget(), QString("%1 Config" ).arg( p->friendlyName() ), this, Qt::Sheet );
dialog->setProperty( "sipplugin", QVariant::fromValue< QObject* >( p ) );
connect( dialog, SIGNAL( finished( int ) ), this, SLOT( sipCreateConfigClosed( int ) ) );
dialog->show();
#else
DelegateConfigWrapper dialog( p->configWidget(), QString("%1 Config" ).arg( p->friendlyName() ), this );
QWeakPointer< DelegateConfigWrapper > watcher( &dialog );
int ret = dialog.exec();
@@ -544,13 +553,44 @@ SettingsDialog::sipFactoryClicked( SipPluginFactory* factory )
// canceled, delete it
added = false;
}
handleSipPluginAdded( p, added );
#endif
} else {
// no config, so just add it
added = true;
TomahawkSettings::instance()->addSipPlugin( p->pluginId() );
SipHandler::instance()->addSipPlugin( p );
handleSipPluginAdded( p, added );
}
}
void
SettingsDialog::sipCreateConfigClosed( int finished )
{
DelegateConfigWrapper* dialog = qobject_cast< DelegateConfigWrapper* >( sender() );
SipPlugin* p = qobject_cast< SipPlugin* >( dialog->property( "sipplugin" ).value< QObject* >() );
Q_ASSERT( p );
bool added = false;
if( finished == QDialog::Accepted ) {
p->saveConfig();
TomahawkSettings::instance()->addSipPlugin( p->pluginId() );
SipHandler::instance()->addSipPlugin( p );
added = true;
}
handleSipPluginAdded( p, added );
}
void
SettingsDialog::handleSipPluginAdded( SipPlugin* p, bool added )
{
SipPluginFactory* f = SipHandler::instance()->factoryFromPlugin( p );
if( added && f && f->isUnique() ) {
// remove from actions list
@@ -570,6 +610,7 @@ SettingsDialog::sipFactoryClicked( SipPluginFactory* factory )
}
}
void
SettingsDialog::sipContextMenuRequest( const QPoint& p )
{
@@ -625,9 +666,9 @@ ProxyDialog::ProxyDialog( QWidget *parent )
, ui( new Ui::ProxyDialog )
{
ui->setupUi( this );
// ugly, I know, but...
int i = 0;
ui->typeBox->insertItem( i, "No Proxy", QNetworkProxy::NoProxy );
m_forwardMap[ QNetworkProxy::NoProxy ] = i;
@@ -637,9 +678,9 @@ ProxyDialog::ProxyDialog( QWidget *parent )
m_forwardMap[ QNetworkProxy::Socks5Proxy ] = i;
m_backwardMap[ i ] = QNetworkProxy::Socks5Proxy;
i++;
TomahawkSettings* s = TomahawkSettings::instance();
ui->typeBox->setCurrentIndex( m_forwardMap[s->proxyType()] );
ui->hostLineEdit->setText( s->proxyHost() );
ui->portSpinBox->setValue( s->proxyPort() );
@@ -681,18 +722,18 @@ ProxyDialog::proxyTypeChangedSlot( int index )
ui->passwordLineEdit->setEnabled( true );
ui->checkBoxUseProxyForDns->setEnabled( true );
ui->noHostLineEdit->setEnabled( true );
}
}
}
void
ProxyDialog::saveSettings()
{
qDebug() << Q_FUNC_INFO;
//First set settings
TomahawkSettings* s = TomahawkSettings::instance();
s->setProxyHost( ui->hostLineEdit->text() );
int port = ui->portSpinBox->value();
s->setProxyPort( port );
s->setProxyNoProxyHosts( ui->noHostLineEdit->text() );
@@ -700,10 +741,10 @@ ProxyDialog::saveSettings()
s->setProxyPassword( ui->passwordLineEdit->text() );
s->setProxyType( ui->typeBox->itemData( ui->typeBox->currentIndex() ).toInt() );
s->setProxyDns( ui->checkBoxUseProxyForDns->checkState() == Qt::Checked );
if( s->proxyHost().isEmpty() )
return;
TomahawkUtils::NetworkProxyFactory* proxyFactory = new TomahawkUtils::NetworkProxyFactory();
QNetworkProxy proxy( static_cast<QNetworkProxy::ProxyType>(s->proxyType()), s->proxyHost(), s->proxyPort(), s->proxyUsername(), s->proxyPassword() );
proxyFactory->setProxy( proxy );

View File

@@ -48,7 +48,7 @@ public:
private slots:
void proxyTypeChangedSlot( int index );
private:
Ui::ProxyDialog* ui;
QHash<int,int> m_forwardMap;
@@ -95,12 +95,14 @@ private slots:
// dialog slots
void resolverConfigClosed( int value );
void sipConfigClosed( int value );
void sipCreateConfigClosed( int value );
void changePage( QListWidgetItem*, QListWidgetItem* );
private:
void createIcons();
void setupSipButtons();
void handleSipPluginAdded( SipPlugin* p, bool added );
Ui_StackedSettingsDialog* ui;

View File

@@ -143,10 +143,20 @@ SourceTreeView::setupMenus()
m_copyPlaylistAction = m_playlistMenu.addAction( tr( "&Copy Link" ) );
m_deletePlaylistAction = m_playlistMenu.addAction( tr( "&Delete %1" ).arg( SourcesModel::rowTypeToString( type ) ) );
m_roPlaylistMenu.addAction( m_copyPlaylistAction );
QString addToText = QString( "Add to my %1" );
if ( type == SourcesModel::StaticPlaylist )
addToText = addToText.arg( "Playlists" );
if ( type == SourcesModel::AutomaticPlaylist )
addToText = addToText.arg( "Automatic Playlists" );
else if ( type == SourcesModel::Station )
addToText = addToText.arg( "Stations" );
m_addToLocalAction = m_roPlaylistMenu.addAction( tr( addToText.toUtf8(), "Adds the given playlist, dynamic playlist, or station to the users's own list" ) );
m_roPlaylistMenu.addAction( m_copyPlaylistAction );
m_deletePlaylistAction->setEnabled( !readonly );
m_renamePlaylistAction->setEnabled( !readonly );
m_addToLocalAction->setEnabled( readonly );
if ( type == SourcesModel::StaticPlaylist )
m_copyPlaylistAction->setText( tr( "&Export Playlist" ) );
@@ -155,6 +165,7 @@ SourceTreeView::setupMenus()
connect( m_renamePlaylistAction, SIGNAL( triggered() ), SLOT( renamePlaylist() ) );
connect( m_deletePlaylistAction, SIGNAL( triggered() ), SLOT( deletePlaylist() ) );
connect( m_copyPlaylistAction, SIGNAL( triggered() ), SLOT( copyPlaylistLink() ) );
connect( m_addToLocalAction, SIGNAL( triggered() ), SLOT( addToLocal() ) );
}
@@ -259,6 +270,36 @@ SourceTreeView::copyPlaylistLink()
}
}
void SourceTreeView::addToLocal()
{
QModelIndex idx = m_contextMenuIndex;
if ( !idx.isValid() )
return;
SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
if( type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station )
{
DynamicPlaylistItem* item = itemFromIndex< DynamicPlaylistItem >( m_contextMenuIndex );
dynplaylist_ptr playlist = item->dynPlaylist();
// copy to a link and then generate a new playlist from that
// this way we cheaply regenerate the needed controls
QString link = GlobalActionManager::instance()->copyPlaylistToClipboard( playlist );
dynplaylist_ptr p = GlobalActionManager::instance()->loadDynamicPlaylist( link, type == SourcesModel::Station );
} else if ( type == SourcesModel::StaticPlaylist )
{
PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
playlist_ptr playlist = item->playlist();
// just create the new playlist with the same values
QList< query_ptr > queries;
foreach( const plentry_ptr& e, playlist->entries() )
queries << e->query();
playlist_ptr newpl = Playlist::create( SourceList::instance()->getLocal(), uuid(), playlist->title(), playlist->info(), playlist->creator(), playlist->shared(), queries );
}
}
void
SourceTreeView::renamePlaylist()

View File

@@ -54,6 +54,7 @@ private slots:
void loadPlaylist();
void deletePlaylist( const QModelIndex& = QModelIndex() );
void copyPlaylistLink();
void addToLocal();
void onCustomContextMenu( const QPoint& pos );
protected:
@@ -84,6 +85,7 @@ private:
QAction* m_renamePlaylistAction;
QAction* m_deletePlaylistAction;
QAction* m_copyPlaylistAction;
QAction* m_addToLocalAction;
bool m_dragging;
QRect m_dropRect;

View File

@@ -57,6 +57,7 @@
#include "diagnosticsdialog.h"
#include "tomahawksettings.h"
#include "sourcelist.h"
#include "PipelineStatusView.h"
#include "transferview.h"
#include "tomahawktrayicon.h"
#include "playlist/dynamic/GeneratorInterface.h"
@@ -107,18 +108,19 @@ TomahawkWindow::TomahawkWindow( QWidget* parent )
sidebar->setOrientation( Qt::Vertical );
sidebar->setChildrenCollapsible( false );
sidebar->setGreedyWidget( 0 );
sidebar->setStretchFactor( 0, 3 );
sidebar->setStretchFactor( 1, 1 );
m_sourcetree = new SourceTreeView();
TransferView* transferView = new TransferView();
TransferView* transferView = new TransferView( sidebar );
PipelineStatusView* pipelineView = new PipelineStatusView( sidebar );
connect( ui->actionHideOfflineSources, SIGNAL( triggered() ), m_sourcetree, SLOT( hideOfflineSources() ) );
connect( ui->actionShowOfflineSources, SIGNAL( triggered() ), m_sourcetree, SLOT( showOfflineSources() ) );
sidebar->addWidget( m_sourcetree );
sidebar->addWidget( transferView );
sidebar->addWidget( pipelineView );
sidebar->hide( 1, false );
sidebar->hide( 2, false );
/* QWidget* buttonWidget = new QWidget();
buttonWidget->setLayout( new QVBoxLayout() );
@@ -233,7 +235,6 @@ TomahawkWindow::loadSettings()
restoreState( s->mainWindowState() );
if ( !s->mainWindowSplitterState().isEmpty() )
ui->splitter->restoreState( s->mainWindowSplitterState() );
#ifdef QT_MAC_USE_COCOA
if( workaround ) {
// Make it visible again

View File

@@ -1,5 +1,5 @@
/* === 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
@@ -80,12 +80,6 @@ TransferView::streamFinished( StreamConnection* sc )
emit showWidget();
else
emit hideWidget();
/* if ( m_index.contains( sc ) )
{
int i = m_index.value( sc );
m_tree->invisibleRootItem()->child( i )->setText( 1, tr( "Finished" ) );
}*/
}

View File

@@ -1,5 +1,5 @@
/* === 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
@@ -32,7 +32,7 @@ class TransferView : public AnimatedWidget
Q_OBJECT
public:
explicit TransferView( AnimatedSplitter* parent = 0 );
explicit TransferView( AnimatedSplitter* parent );
virtual ~TransferView()
{
qDebug() << Q_FUNC_INFO;

222
src/win/qtwin.cpp Normal file
View File

@@ -0,0 +1,222 @@
/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Use, modification and distribution is allowed without limitation,
** warranty, liability or support of any kind.
**
****************************************************************************/
#include "qtwin.h"
#include <QLibrary>
#include <QApplication>
#include <QWidget>
#include <QList>
#include <QPointer>
#ifdef Q_WS_WIN
#include <qt_windows.h>
// Blur behind data structures
#define DWM_BB_ENABLE 0x00000001 // fEnable has been specified
#define DWM_BB_BLURREGION 0x00000002 // hRgnBlur has been specified
#define DWM_BB_TRANSITIONONMAXIMIZED 0x00000004 // fTransitionOnMaximized has ben specified
#define WM_DWMCOMPOSITIONCHANGED 0x031E // Composition changed window message
typedef struct _DWM_BLURBEHIND
{
DWORD dwFlags;
BOOL fEnable;
HRGN hRgnBlur;
BOOL fTransitionOnMaximized;
} DWM_BLURBEHIND, *PDWM_BLURBEHIND;
typedef struct _MARGINS
{
int cxLeftWidth;
int cxRightWidth;
int cyTopHeight;
int cyBottomHeight;
} MARGINS, *PMARGINS;
typedef HRESULT (WINAPI *PtrDwmIsCompositionEnabled)(BOOL* pfEnabled);
typedef HRESULT (WINAPI *PtrDwmExtendFrameIntoClientArea)(HWND hWnd, const MARGINS* pMarInset);
typedef HRESULT (WINAPI *PtrDwmEnableBlurBehindWindow)(HWND hWnd, const DWM_BLURBEHIND* pBlurBehind);
typedef HRESULT (WINAPI *PtrDwmGetColorizationColor)(DWORD *pcrColorization, BOOL *pfOpaqueBlend);
static PtrDwmIsCompositionEnabled pDwmIsCompositionEnabled= 0;
static PtrDwmEnableBlurBehindWindow pDwmEnableBlurBehindWindow = 0;
static PtrDwmExtendFrameIntoClientArea pDwmExtendFrameIntoClientArea = 0;
static PtrDwmGetColorizationColor pDwmGetColorizationColor = 0;
/*
* Internal helper class that notifies windows if the
* DWM compositing state changes and updates the widget
* flags correspondingly.
*/
class WindowNotifier : public QWidget
{
public:
WindowNotifier() { winId(); }
void addWidget(QWidget *widget) { widgets.append(widget); }
void removeWidget(QWidget *widget) { widgets.removeAll(widget); }
bool winEvent(MSG *message, long *result);
private:
QWidgetList widgets;
};
static bool resolveLibs()
{
if (!pDwmIsCompositionEnabled) {
QLibrary dwmLib(QString::fromAscii("dwmapi"));
pDwmIsCompositionEnabled =(PtrDwmIsCompositionEnabled)dwmLib.resolve("DwmIsCompositionEnabled");
pDwmExtendFrameIntoClientArea = (PtrDwmExtendFrameIntoClientArea)dwmLib.resolve("DwmExtendFrameIntoClientArea");
pDwmEnableBlurBehindWindow = (PtrDwmEnableBlurBehindWindow)dwmLib.resolve("DwmEnableBlurBehindWindow");
pDwmGetColorizationColor = (PtrDwmGetColorizationColor)dwmLib.resolve("DwmGetColorizationColor");
}
return pDwmIsCompositionEnabled != 0;
}
#endif
/*!
* Chekcs and returns true if Windows DWM composition
* is currently enabled on the system.
*
* To get live notification on the availability of
* this feature, you will currently have to
* reimplement winEvent() on your widget and listen
* for the WM_DWMCOMPOSITIONCHANGED event to occur.
*
*/
bool QtWin::isCompositionEnabled()
{
#ifdef Q_WS_WIN
if (resolveLibs()) {
HRESULT hr = S_OK;
BOOL isEnabled = false;
hr = pDwmIsCompositionEnabled(&isEnabled);
if (SUCCEEDED(hr))
return isEnabled;
}
#endif
return false;
}
/*!
* Enables Blur behind on a Widget.
*
* \a enable tells if the blur should be enabled or not
*/
bool QtWin::enableBlurBehindWindow(QWidget *widget, bool enable)
{
Q_ASSERT(widget);
bool result = false;
#ifdef Q_WS_WIN
if (resolveLibs()) {
DWM_BLURBEHIND bb = {0};
HRESULT hr = S_OK;
bb.fEnable = enable;
bb.dwFlags = DWM_BB_ENABLE;
bb.hRgnBlur = NULL;
widget->setAttribute(Qt::WA_TranslucentBackground, enable);
widget->setAttribute(Qt::WA_NoSystemBackground, enable);
hr = pDwmEnableBlurBehindWindow(widget->winId(), &bb);
if (SUCCEEDED(hr)) {
result = true;
windowNotifier()->addWidget(widget);
}
}
#endif
return result;
}
/*!
* ExtendFrameIntoClientArea.
*
* This controls the rendering of the frame inside the window.
* Note that passing margins of -1 (the default value) will completely
* remove the frame from the window.
*
* \note you should not call enableBlurBehindWindow before calling
* this functions
*
* \a enable tells if the blur should be enabled or not
*/
bool QtWin::extendFrameIntoClientArea(QWidget *widget, int left, int top, int right, int bottom)
{
Q_ASSERT(widget);
Q_UNUSED(left);
Q_UNUSED(top);
Q_UNUSED(right);
Q_UNUSED(bottom);
bool result = false;
#ifdef Q_WS_WIN
if (resolveLibs()) {
QLibrary dwmLib(QString::fromAscii("dwmapi"));
HRESULT hr = S_OK;
MARGINS m = {left, top, right, bottom};
hr = pDwmExtendFrameIntoClientArea(widget->winId(), &m);
if (SUCCEEDED(hr)) {
result = true;
windowNotifier()->addWidget(widget);
}
widget->setAttribute(Qt::WA_TranslucentBackground, result);
}
#endif
return result;
}
/*!
* Returns the current colorizationColor for the window.
*
* \a enable tells if the blur should be enabled or not
*/
QColor QtWin::colorizatinColor()
{
QColor resultColor = QApplication::palette().window().color();
#ifdef Q_WS_WIN
if (resolveLibs()) {
DWORD color = 0;
BOOL opaque = FALSE;
QLibrary dwmLib(QString::fromAscii("dwmapi"));
HRESULT hr = S_OK;
hr = pDwmGetColorizationColor(&color, &opaque);
if (SUCCEEDED(hr))
resultColor = QColor(color);
}
#endif
return resultColor;
}
#ifdef Q_WS_WIN
WindowNotifier *QtWin::windowNotifier()
{
static WindowNotifier *windowNotifierInstance = 0;
if (!windowNotifierInstance)
windowNotifierInstance = new WindowNotifier;
return windowNotifierInstance;
}
/* Notify all enabled windows that the DWM state changed */
bool WindowNotifier::winEvent(MSG *message, long *result)
{
if (message && message->message == WM_DWMCOMPOSITIONCHANGED) {
bool compositionEnabled = QtWin::isCompositionEnabled();
foreach(QWidget * widget, widgets) {
if (widget) {
widget->setAttribute(Qt::WA_NoSystemBackground, compositionEnabled);
}
widget->update();
}
}
return QWidget::winEvent(message, result);
}
#endif

37
src/win/qtwin.h Normal file
View File

@@ -0,0 +1,37 @@
/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Use, modification and distribution is allowed without limitation,
** warranty, liability or support of any kind.
**
****************************************************************************/
#ifndef QTWIN_H
#define QTWIN_H
#include <QColor>
#include <QWidget>
/**
* This is a helper class for using the Desktop Window Manager
* functionality on Windows 7 and Windows Vista. On other platforms
* these functions will simply not do anything.
*/
class WindowNotifier;
class QtWin
{
public:
static bool enableBlurBehindWindow(QWidget *widget, bool enable = true);
static bool extendFrameIntoClientArea(QWidget *widget,
int left = -1, int top = -1,
int right = -1, int bottom = -1);
static bool isCompositionEnabled();
static QColor colorizatinColor();
private:
static WindowNotifier *windowNotifier();
};
#endif // QTWIN_H

View File

@@ -1,2 +1,5 @@
ADD_SUBDIRECTORY( qxt )
ADD_SUBDIRECTORY( liblastfm2 )
IF( APPLE )
ADD_SUBDIRECTORY( SPMediaKeyTap )
ENDIF()

13
thirdparty/SPMediaKeyTap/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,13 @@
set(SPMEDIAKEY-SOURCES
SPMediaKeyTap.m
SPInvocationGrabbing/NSObject+SPInvocationGrabbing.m
)
set(SPMEDIAKEY-HEADERS
SPMediaKeyTap.h
SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h
)
ADD_LIBRARY(SPMediaKeyTap STATIC
${SPMEDIAKEY-SOURCES}
)

8
thirdparty/SPMediaKeyTap/LICENSE vendored Normal file
View File

@@ -0,0 +1,8 @@
Copyright (c) 2011, Joachim Bengtsson
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Neither the name of the organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

12
thirdparty/SPMediaKeyTap/README.md vendored Normal file
View File

@@ -0,0 +1,12 @@
SPMediaKeyTap
=============
`SPMediaKeyTap` abstracts a `CGEventHook` and other nastiness in order to give you a relatively simple API to receive media key events (prev/next/playpause, on F7 to F9 on modern MacBook Pros) exclusively, without them reaching other applications like iTunes. `SPMediaKeyTap` is clever enough to resign its exclusive lock on media keys by looking for which application was active most recently: if that application is in `SPMediaKeyTap`'s whitelist, it will resign the keys. This is similar to the behavior of Apple's applications collaborating on media key handling exclusivity, but unfortunately, Apple are not exposing any APIs allowing third-parties to join in on this collaboration.
For now, the whitelist is just a hardcoded array in `+[SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers]`. If your app starts using `SPMediaKeyTap`, please [mail me](mailto:nevyn@spotify.com) your bundle ID, and I'll include it in the canonical repository. This is a bad solution; a better solution would be to use distributed notifications to collaborate in creating this whitelist at runtime. Hopefully someone'll have the time and energy to write this soon.
In `Example/SPMediaKeyTapExampleAppDelegate.m` is an example of both how you use `SPMediaKeyTap`, and how you handle the semi-private `NSEvent` subtypes involved in media keys, including on how to fall back to non-event tap handling of these events.
`SPMediaKeyTap` and other `CGEventHook`s on the event type `NSSystemDefined` is known to interfere with each other and applications doing weird stuff with mouse input, because mouse clicks are also part of the `NSSystemDefined` category. The single issue we have had reported here at Spotify is Adobe Fireworks, in which item selection stops working with `SPMediaKeyTap` is active.
`SPMediaKeyTap` requires 10.5 to work, and disables itself on 10.4.

View File

@@ -0,0 +1,30 @@
#import <Foundation/Foundation.h>
@interface SPInvocationGrabber : NSObject {
id _object;
NSInvocation *_invocation;
int frameCount;
char **frameStrings;
BOOL backgroundAfterForward;
BOOL onMainAfterForward;
BOOL waitUntilDone;
}
-(id)initWithObject:(id)obj;
-(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack;
@property (readonly, retain, nonatomic) id object;
@property (readonly, retain, nonatomic) NSInvocation *invocation;
@property BOOL backgroundAfterForward;
@property BOOL onMainAfterForward;
@property BOOL waitUntilDone;
-(void)invoke; // will release object and invocation
-(void)printBacktrace;
-(void)saveBacktrace;
@end
@interface NSObject (SPInvocationGrabbing)
-(id)grab;
-(id)invokeAfter:(NSTimeInterval)delta;
-(id)nextRunloop;
-(id)inBackground;
-(id)onMainAsync:(BOOL)async;
@end

View File

@@ -0,0 +1,128 @@
#import "NSObject+SPInvocationGrabbing.h"
#import <execinfo.h>
#pragma mark Invocation grabbing
@interface SPInvocationGrabber ()
@property (readwrite, retain, nonatomic) id object;
@property (readwrite, retain, nonatomic) NSInvocation *invocation;
@end
@implementation SPInvocationGrabber
- (id)initWithObject:(id)obj;
{
return [self initWithObject:obj stacktraceSaving:YES];
}
-(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack;
{
self.object = obj;
if(saveStack)
[self saveBacktrace];
return self;
}
-(void)dealloc;
{
free(frameStrings);
self.object = nil;
self.invocation = nil;
[super dealloc];
}
@synthesize invocation = _invocation, object = _object;
@synthesize backgroundAfterForward, onMainAfterForward, waitUntilDone;
- (void)runInBackground;
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
@try {
[self invoke];
}
@finally {
[pool drain];
}
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation retainArguments];
anInvocation.target = _object;
self.invocation = anInvocation;
if(backgroundAfterForward)
[NSThread detachNewThreadSelector:@selector(runInBackground) toTarget:self withObject:nil];
else if(onMainAfterForward)
[self performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:waitUntilDone];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)inSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:inSelector];
if (signature == NULL)
signature = [_object methodSignatureForSelector:inSelector];
return signature;
}
- (void)invoke;
{
@try {
[_invocation invoke];
}
@catch (NSException * e) {
NSLog(@"SPInvocationGrabber's target raised %@:\n\t%@\nInvocation was originally scheduled at:", e.name, e);
[self printBacktrace];
printf("\n");
[e raise];
}
self.invocation = nil;
self.object = nil;
}
-(void)saveBacktrace;
{
void *backtraceFrames[128];
frameCount = backtrace(&backtraceFrames[0], 128);
frameStrings = backtrace_symbols(&backtraceFrames[0], frameCount);
}
-(void)printBacktrace;
{
int x;
for(x = 3; x < frameCount; x++) {
if(frameStrings[x] == NULL) { break; }
printf("%s\n", frameStrings[x]);
}
}
@end
@implementation NSObject (SPInvocationGrabbing)
-(id)grab;
{
return [[[SPInvocationGrabber alloc] initWithObject:self] autorelease];
}
-(id)invokeAfter:(NSTimeInterval)delta;
{
id grabber = [self grab];
[NSTimer scheduledTimerWithTimeInterval:delta target:grabber selector:@selector(invoke) userInfo:nil repeats:NO];
return grabber;
}
- (id)nextRunloop;
{
return [self invokeAfter:0];
}
-(id)inBackground;
{
SPInvocationGrabber *grabber = [self grab];
grabber.backgroundAfterForward = YES;
return grabber;
}
-(id)onMainAsync:(BOOL)async;
{
SPInvocationGrabber *grabber = [self grab];
grabber.onMainAfterForward = YES;
grabber.waitUntilDone = !async;
return grabber;
}
@end

View File

@@ -0,0 +1,28 @@
// A
+(UIView*)flashAt:(CGRect)r in:(UIView*)parent color:(UIColor*)color;
{
float duration = 0.5;
UIView *flash = [[[UIView alloc] initWithFrame:r] autorelease];
flash.backgroundColor = color;
[parent addSubview:flash];
[[flash invokeAfter:duration+0.1] removeFromSuperview];
[UIView beginAnimations:@"SPFlash" context:NULL];
[UIView setAnimationDuration:duration];
flash.alpha = 0.0;
[UIView commitAnimations];
return flash;
}
// B
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Force the animation to happen by calling this method again after a small
// delay - see http://blog.instapaper.com/post/53568356
[[self nextRunloop] delayedTableViewDidSelectRowAtIndexPath: indexPath];
}
// C
[[tableView invokeAfter:0.15] selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
[[tableView invokeAfter:0.30] deselectRowAtIndexPath:indexPath animated:YES];
[[tableView invokeAfter:0.45] selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];

View File

@@ -0,0 +1,12 @@
@interface MyClass : NSObject
-(BOOL)areTheNewViewersGoneYet:(Duck*)duck;
@end
...
MyClass *myInstance = [[MyClass alloc] init];
id invocationGrabber = [[[SPInvocationGrabber alloc] initWithTarget:myInstance] autorelease];
[invocationGrabber areTheNewViewersGoneYet:[Duck yellowDuck]]; // line 9
NSInvocation *invocationForAreTheNewViewersGoneYet = [invocationGrabber invocation];

View File

@@ -0,0 +1,38 @@
#import <Cocoa/Cocoa.h>
#import "NSObject+SPInvocationGrabbing.h"
@interface Foo : NSObject {
int a;
}
-(void)startIt;
-(void)theBackgroundStuff;
-(void)theForegroundStuff;
@end
@implementation Foo
-(void)startIt;
{
NSLog(@"Starting out on the main thread...");
a = 3;
[[self inBackground] theBackgroundStuff];
}
-(void)theBackgroundStuff;
{
NSLog(@"Woah, this is a background thread!");
a += 6;
[[self onMainAsync:YES] theForegroundStuff];
}
-(void)theForegroundStuff;
{
NSLog(@"Hey presto: %d", a);
}
@end
int main() {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
Foo *foo = [Foo new];
[foo startIt];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
[pool release];
return 0;
}

View File

@@ -0,0 +1,34 @@
#include <Cocoa/Cocoa.h>
#import <IOKit/hidsystem/ev_keymap.h>
#import <Carbon/Carbon.h>
// http://overooped.com/post/2593597587/mediakeys
#define SPSystemDefinedEventMediaKeys 8
@interface SPMediaKeyTap : NSObject {
EventHandlerRef _app_switching_ref;
EventHandlerRef _app_terminating_ref;
CFMachPortRef _eventPort;
CFRunLoopSourceRef _eventPortSource;
CFRunLoopRef _tapThreadRL;
BOOL _shouldInterceptMediaKeyEvents;
id _delegate;
// The app that is frontmost in this list owns media keys
NSMutableArray *_mediaKeyAppList;
}
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers;
-(id)initWithDelegate:(id)delegate;
+(BOOL)usesGlobalMediaKeyTap;
-(void)startWatchingMediaKeys;
-(void)stopWatchingMediaKeys;
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event;
@end
@interface NSObject (SPMediaKeyTapDelegate)
-(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
@end
extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey;

300
thirdparty/SPMediaKeyTap/SPMediaKeyTap.m vendored Normal file
View File

@@ -0,0 +1,300 @@
// Copyright (c) 2010 Spotify AB
#import "SPMediaKeyTap.h"
#import "SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h" // https://gist.github.com/511181, in submodule
@interface SPMediaKeyTap ()
-(BOOL)shouldInterceptMediaKeyEvents;
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting;
-(void)startWatchingAppSwitching;
-(void)stopWatchingAppSwitching;
-(void)eventTapThread;
@end
static SPMediaKeyTap *singleton = nil;
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData);
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData);
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon);
// Inspired by http://gist.github.com/546311
@implementation SPMediaKeyTap
#pragma mark -
#pragma mark Setup and teardown
-(id)initWithDelegate:(id)delegate;
{
_delegate = delegate;
[self startWatchingAppSwitching];
singleton = self;
_mediaKeyAppList = [NSMutableArray new];
return self;
}
-(void)dealloc;
{
[self stopWatchingMediaKeys];
[self stopWatchingAppSwitching];
[_mediaKeyAppList release];
[super dealloc];
}
-(void)startWatchingAppSwitching;
{
// Listen to "app switched" event, so that we don't intercept media keys if we
// weren't the last "media key listening" app to be active
EventTypeSpec eventType = { kEventClassApplication, kEventAppFrontSwitched };
OSStatus err = InstallApplicationEventHandler(NewEventHandlerUPP(appSwitched), 1, &eventType, self, &_app_switching_ref);
assert(err == noErr);
eventType.eventKind = kEventAppTerminated;
err = InstallApplicationEventHandler(NewEventHandlerUPP(appTerminated), 1, &eventType, self, &_app_terminating_ref);
assert(err == noErr);
}
-(void)stopWatchingAppSwitching;
{
if(!_app_switching_ref) return;
RemoveEventHandler(_app_switching_ref);
_app_switching_ref = NULL;
}
-(void)startWatchingMediaKeys;{
[self setShouldInterceptMediaKeyEvents:YES];
// Add an event tap to intercept the system defined media key events
_eventPort = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(NX_SYSDEFINED),
tapEventCallback,
self);
assert(_eventPort != NULL);
_eventPortSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, _eventPort, 0);
assert(_eventPortSource != NULL);
// Let's do this in a separate thread so that a slow app doesn't lag the event tap
[NSThread detachNewThreadSelector:@selector(eventTapThread) toTarget:self withObject:nil];
}
-(void)stopWatchingMediaKeys;
{
// TODO<nevyn>: Shut down thread, remove event tap port and source
}
#pragma mark -
#pragma mark Accessors
+(BOOL)usesGlobalMediaKeyTap
{
#ifdef _DEBUG
// breaking in gdb with a key tap inserted sometimes locks up all mouse and keyboard input forever, forcing reboot
return NO;
#else
// XXX(nevyn): MediaKey event tap doesn't work on 10.4, feel free to figure out why if you have the energy.
return floor(NSAppKitVersionNumber) >= 949/*NSAppKitVersionNumber10_5*/;
#endif
}
+ (NSArray*)defaultMediaKeyUserBundleIdentifiers;
{
return [NSArray arrayWithObjects:
[[NSBundle mainBundle] bundleIdentifier], // your app
@"com.spotify.client",
@"com.apple.iTunes",
@"com.apple.QuickTimePlayerX",
@"com.apple.quicktimeplayer",
@"com.apple.iWork.Keynote",
@"com.apple.iPhoto",
@"org.videolan.vlc",
@"com.apple.Aperture",
@"com.plexsquared.Plex",
@"com.soundcloud.desktop",
@"com.macromedia.fireworks", // the tap messes up their mouse input
nil
];
}
-(BOOL)shouldInterceptMediaKeyEvents;
{
BOOL shouldIntercept = NO;
@synchronized(self) {
shouldIntercept = _shouldInterceptMediaKeyEvents;
}
return shouldIntercept;
}
-(void)pauseTapOnTapThread:(BOOL)yeahno;
{
CGEventTapEnable(self->_eventPort, yeahno);
}
-(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting;
{
BOOL oldSetting;
@synchronized(self) {
oldSetting = _shouldInterceptMediaKeyEvents;
_shouldInterceptMediaKeyEvents = newSetting;
}
if(_tapThreadRL && oldSetting != newSetting) {
id grab = [self grab];
[grab pauseTapOnTapThread:newSetting];
NSTimer *timer = [NSTimer timerWithTimeInterval:0 invocation:[grab invocation] repeats:NO];
CFRunLoopAddTimer(_tapThreadRL, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
}
}
#pragma mark
#pragma mark -
#pragma mark Event tap callbacks
// Note: method called on background thread
static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
{
SPMediaKeyTap *self = refcon;
if(type == kCGEventTapDisabledByTimeout) {
NSLog(@"Media key event tap was disabled by timeout");
CGEventTapEnable(self->_eventPort, TRUE);
return event;
} else if(type == kCGEventTapDisabledByUserInput) {
// Was disabled manually by -[pauseTapOnTapThread]
return event;
}
NSEvent *nsEvent = nil;
@try {
nsEvent = [NSEvent eventWithCGEvent:event];
}
@catch (NSException * e) {
NSLog(@"Strange CGEventType: %d: %@", type, e);
assert(0);
return event;
}
if (type != NX_SYSDEFINED || [nsEvent subtype] != SPSystemDefinedEventMediaKeys)
return event;
int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16);
if (keyCode != NX_KEYTYPE_PLAY && keyCode != NX_KEYTYPE_FAST && keyCode != NX_KEYTYPE_REWIND)
return event;
if (![self shouldInterceptMediaKeyEvents])
return event;
[nsEvent retain]; // matched in handleAndReleaseMediaKeyEvent:
[self performSelectorOnMainThread:@selector(handleAndReleaseMediaKeyEvent:) withObject:nsEvent waitUntilDone:NO];
return NULL;
}
static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
CGEventRef ret = tapEventCallback2(proxy, type, event, refcon);
[pool drain];
return ret;
}
// event will have been retained in the other thread
-(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event {
[event autorelease];
[_delegate mediaKeyTap:self receivedMediaKeyEvent:event];
}
-(void)eventTapThread;
{
_tapThreadRL = CFRunLoopGetCurrent();
CFRunLoopAddSource(_tapThreadRL, _eventPortSource, kCFRunLoopCommonModes);
CFRunLoopRun();
}
#pragma mark Task switching callbacks
NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey = @"SPApplicationsNeedingMediaKeys";
-(void)mediaKeyAppListChanged;
{
if([_mediaKeyAppList count] == 0) return;
/*NSLog(@"--");
int i = 0;
for (NSValue *psnv in _mediaKeyAppList) {
ProcessSerialNumber psn; [psnv getValue:&psn];
NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary(
&psn,
kProcessDictionaryIncludeAllInformationMask
) autorelease];
NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey];
NSLog(@"%d: %@", i++, bundleIdentifier);
}*/
ProcessSerialNumber mySerial, topSerial;
GetCurrentProcess(&mySerial);
[[_mediaKeyAppList objectAtIndex:0] getValue:&topSerial];
Boolean same;
OSErr err = SameProcess(&mySerial, &topSerial, &same);
[self setShouldInterceptMediaKeyEvents:(err == noErr && same)];
}
-(void)appIsNowFrontmost:(ProcessSerialNumber)psn;
{
NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary(
&psn,
kProcessDictionaryIncludeAllInformationMask
) autorelease];
NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey];
NSArray *whitelistIdentifiers = [[NSUserDefaults standardUserDefaults] arrayForKey:kMediaKeyUsingBundleIdentifiersDefaultsKey];
if(![whitelistIdentifiers containsObject:bundleIdentifier]) return;
[_mediaKeyAppList removeObject:psnv];
[_mediaKeyAppList insertObject:psnv atIndex:0];
[self mediaKeyAppListChanged];
}
-(void)appTerminated:(ProcessSerialNumber)psn;
{
NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)];
[_mediaKeyAppList removeObject:psnv];
[self mediaKeyAppListChanged];
}
static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
{
SPMediaKeyTap *self = (id)userData;
ProcessSerialNumber newSerial;
GetFrontProcess(&newSerial);
[self appIsNowFrontmost:newSerial];
return CallNextEventHandler(nextHandler, evt);
}
static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData)
{
SPMediaKeyTap *self = (id)userData;
ProcessSerialNumber deadPSN;
GetEventParameter(
evt,
kEventParamProcessID,
typeProcessSerialNumber,
NULL,
sizeof(deadPSN),
NULL,
&deadPSN
);
[self appTerminated:deadPSN];
return CallNextEventHandler(nextHandler, evt);
}
@end

View File

@@ -0,0 +1,25 @@
-(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
{
assert([event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys);
int keyCode = (([event data1] & 0xFFFF0000) >> 16);
int keyFlags = ([event data1] & 0x0000FFFF);
int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA;
int keyRepeat = (keyFlags & 0x1);
if (keyState == 1 && windowController != NULL) {
switch (keyCode) {
case NX_KEYTYPE_PLAY:
... return;
case NX_KEYTYPE_FAST:
... return;
case NX_KEYTYPE_REWIND:
... return;
}
}
}