moodbar.cpp
来自「Amarok是一款在LINUX或其他类UNIX操作系统中运行的音频播放器软件。 」· C++ 代码 · 共 1,387 行 · 第 1/4 页
CPP
1,387 行
/*************************************************************************** moodbar.cpp - description ------------------- begin : 6th Nov 2005 copyright : (C) 2006 by Joseph Rabinoff copyright : (C) 2005 by Gav Wood email : bobqwatson@yahoo.com***************************************************************************//*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/// Although the current incarnation of moodbar.cpp shares bits and// pieces of code with Gav Wood's original, it has been completely// rewritten -- the only code I kept was purely algorithmic. Also// lots of Moodbar-related functionality has been moved from other// places to here (all of it really).// The Moodbar is used by small amounts of code in playlistitem.cpp// and sliderwidget.cpp. There are also trivial amounts of support// code in other places.// Moodbar usage// -------------//// The Moodbar is part of the track's metadata, so it's held by a// MetaBundle. The actual Moodbar object is only used to draw a// QPixmap, which it does efficiently -- it caches a pixmap of the// last thing it drew, and just copies that pixmap if the dimensions// have not changed. To use the moodbar, one just needs a few lines of// code, such as the following, based on PrettySlider://// void MyClass::MyClass( void )// {// // This only needs to be done once!// connect( &m_bundle.moodbar(), SIGNAL( jobEvent( int ) ),// SLOT( newMoodData( int ) ) );// }//// void MyClass::newMetaBundle( const MetaBundle &b )// {// m_bundle = b;//// if( !m_bundle.moodbar().dataExists() )// m_bundle.moodbar().load();// else// update();// }//// void MyClass::draw( void )// {// QPixmap toDraw;// if( m_bundle.moodbar().dataExists() )// toDraw = m_bundle.moodbar().draw( width(), height() );// // else draw something else...// }//// void MyClass::newMoodData( int newState )// {// if( newState == Moodbar::JobStateSucceeded )// update();// }//// Explanation://// * In the constructor we listen for the jobEvent() signal from the// Moodbar. The Moodbar emits this signal when an analyzer process// has started or completed and it has loaded its moodbar data.// (This connection will exist for the lifetime of the instance of// MyClass and hence only needs to be created once.)//// * Whenever the MetaBundle associated with this instance of MyClass// is changed, so does the moodbar, so we should reload it. The// dataExists() method is meant to return whether the mood has// already been analyzed for that track (it will always return false// for streaming bundles and the like). If it returns true then the// moodbar has already loaded its data, and can draw it.//// * Otherwise we run the Moodbar's load() method. This method may// be called many times; it will only actually do anything the first// time it's called (unless the moodbar is reset()). Hence it's// totally reasonable to call load() in the draw() method too; this// is in fact what the PlaylistItem does. When load() has completed,// it emits a jobEvent() signal.//// * Note that jobEvent() will also be emitted if there is an error// in analyzing or loading the data, with a state indicating failure.// In this case, subsequent calls to dataExists() will still return// false, and subsequent calls to load() will do nothing.//// Implementation// --------------//// There are two new classes, namely the Moodbar (a member of// MetaBundle), and the MoodServer. The former is the only public// class. In a nutshell, the Moodbar is responsible for reading// and drawing mood data, and the MoodServer is in charge of// queueing analyzer jobs and notifying interested Moodbar's when// their job is done.// The Moodbar class --//// The only public interface to the moodbar system. An unloaded// Moodbar is meant to have a very small footprint, since there are// lots of MetaBundle's floating around that aren't going to be// displayed. Most of the data in loaded Moodbars is implicitly// shared anyway (unless you call detach()), so it's reasonable to// pass them around by value.//// Much care has been taken to absolutely minimize the amount of time// a Moodbar is listening for a signal. The only signal a Moodbar// will connect to is MoodServer::jobEvent; this connection is made// when MoodServer::queueJob() is called, and is disconnected in// slotJobEvent(). The reason for this care is because MetaBundle's,// and hence Moodbar's, are copied around and passed-by-value all the// time, so I wanted to reduce overhead; also QObject::disconnect() is// not reentrant (from what I understand), so we don't want that being// called every time a Moodbar is destroyed! For the same reason, the// PlaylistItem does not listen for the jobEvent() signal; instead it// reimplements the MetaBundle::moodbarJobEvent() virtual method.//// Again for this reason, the individual Moodbar's don't listen for// the App::moodbarPrefs() signal (which is emitted every time the// configuration is changed); thus Moodbar's aren't automatically// updated when the AlterMood variable is changed, for instance. This// is a small annoyance, as the owner of the Moodbar has to listen for// that signal and call reset(). This happens in sliderwidget.cpp and// playlist.cpp.//// A moodbar is always in one of the following states://// Unloaded: A newly-created (or newly reset()) Moodbar is in this// state. The Moodbar remains in this state until// dataExists() or load() is called. Note that load()// will return immediately unless the state is Unloaded.// CantLoad: For some reason we know that we'll never be able to// load the Moodbar, for instance if the parent bundle// describes a streaming source. Most methods will return// immediately in this state.// JobQueued: At some point load() was called, so we queued a job with// the MoodServer which hasn't started yet. In this state,// ~Moodbar(), reset(), etc. knows to dequeue jobs and// disconnect signals.// JobRunning: Our analyzer job is actually running. The moodbar behaves// basically the same as in the JobQueued state; this state// exists so the PlaylistItem knows the difference.// JobFailed: The MoodServer has tried to run our job (or gave up before// trying), and came up empty. This state behaves basically// the same as CantLoad.// Loaded: This is the only state in which draw() will work.////// Note that nothing is done to load until dataExists() is called; this// is because there may very well be MetaBundle's floating around that// aren't displayed in the GUI.//// Important members:// m_bundle: link to the parent bundle// m_data: if we are loaded, this is the contents of the .mood file// m_pixmap: the last time draw() was called, we cached what we drew// here// m_url: cache the URL of our queued job for de-queueing// m_state: our current state// m_mutex: lock for the entire object. The Moodbar object should// be entirely reentrant (but see below), so most methods lock the// object before doing anything. (Of course the calling code has to// be threadsafe for this to mean anything.)//// Important methods://// dataExists(): When this is called, we check if the .mood file// exists for our bundle. If so, we load the corresponding file,// and if all goes well, return true. If our bundle is a streaming// track, or is otherwise unloadable, always return false.//// load(): First run readFile() to see if we can load. If not, then// ask MoodServer to run a job for us. Always changes the state// from Unloaded so subsequent calls to load() do nothing.//// draw(): Draw the moodbar onto a QPixmap. Cache what we drew// so that if draw() is called again with the same dimensions// we don't have to redraw.//// reset(): Reset to the unloaded state. This is basically the same// as calling moodbar = Moodbar().//// (protected) slotJobEvent(): Only run by MoodServer, to notify us// when a job is started or completed. Emits the jobEvent()// signal.//// (private) readFile(): When we think there's a file available, this// method tries to load it. We also do the display-independent// analysis here, namely, calculating the sorting index (for sort-// by-hue in the Playlist), and Making Moodier.// The MoodServer class --//// This is a singleton class. It is responsible for queueing analyzer// jobs requested by Moodbar's, running them, and notifying the// Moodbar's when the job has started and completed, successful or no.// This class is also responsible for remembering if the moodbar// system is totally broken (e.g. if the GStreamer plugins are// missing), notifying the user if such is the case, and refusing to// queue any more jobs. MoodServer should be threadsafe, in that you// should be able to run queueJob() from any thread.//// Jobs are referenced by URL. If a Moodbar tries to queue a job// with the same URL as an existing job, the job will not be re-queued;// instead, each queued job has a refcount, which is increased. This// is to support the de-queueing of jobs when Moodbar's are destroyed;// the use case I have in mind is if the user has the moodbar column// displayed in the playlist, he/she adds 1000 tracks to the playlist// (at which point all the displayed tracks queue moodbar jobs), and// then decides to clear the playlist again. The jobEvent() signal// passes the URL of the job that was completed.//// The analyzer is actually run using a KProcess. ThreadManager::Job// is not a good solution, since we need more flexibility in the// queuing process, and in addition, KProcess'es must be started from// the GUI thread!//// Important members:// m_jobQueue: this is a list of MoodServer::ProcData structures,// which contain the data needed to start and reference// a process, as well as a refcount.// m_currentProcess: the currently-running KProcess, if any.// m_currentData: the ProcData structure for the currently-running// process.// m_moodbarBroken: this is set when there's an error running the analyzer// that indicates the analyzer will never be able to run.// When m_moodbarBroken == true, the MoodServer will refuse// to queue new jobs.// m_mutex: you should be able to run queueJob() from any thread,// so most methods lock the object.//// Important methods://// queueJob(): Add a job to the queue. If the job is being run, do nothing;// if the job is already queued, increase its refcount, and if// m_moodbarBroken == true, do nothing.//// deQueueJob(): Called from ~Moodbar(), for instance. Decreases// the refcount of a job, removing it from the queue when the// refcount hits zero. This won't kill a running process.//// (private slot) slotJobCompleted(): Called when a job finishes. Do some// cleanup, and notify the interested parties. Set m_moodbarBroken if// necessary; otherwise call slotNewJob().//// (private slot) slotNewJob(): Called by slotJobCompleted() and queueJob().// Take a job off the queue and start the KProcess.//// (private slot) slotMoodbarPrefs(): Called when the Amarok config changes.// If the moodbar has been disabled completely, kill the current job// (if any), clear the queue, and notify the interested Moodbar's.//// (private slot) slotFileDeleted(): Called when a music file is deleted, so// we can delete the associated moodbar//// (private slot) slotFileMoved(): Called when a music file is moved, so// we can move the associated moodbar// TODO: off-color single bars in dark areas -- do some interpolation when// averaging. Big jumps in hues when near black.//// BUGS:#define DEBUG_PREFIX "Moodbar"#include <config.h>#include "amarok.h"#include "amarokconfig.h"#include "app.h"#include "collectiondb.h"#include "debug.h"#include "metabundle.h"#include "mountpointmanager.h"#include "statusbar.h"#include <qfile.h>#include <qdir.h> // For QDir::rename()#include <qpainter.h>#include <qtimer.h>#include <kstandarddirs.h>#include <string.h> // for memset()#define CLAMP(n, v, x) ((v) < (n) ? (n) : (v) > (x) ? (x) : (v))#define WEBPAGE "http://amarok.kde.org/wiki/Moodbar"///////////////////////////////////////////////////////////////////////////////// MoodServer class///////////////////////////////////////////////////////////////////////////////MoodServer *MoodServer::instance( void ){ static MoodServer m; return &m;}MoodServer::MoodServer( void ) : m_moodbarBroken( false ) , m_currentProcess( 0 ){ connect( App::instance(), SIGNAL( moodbarPrefs( bool, bool, int, bool ) ), SLOT( slotMoodbarPrefs( bool, bool, int, bool ) ) ); connect( CollectionDB::instance(), SIGNAL( fileMoved( const QString &, const QString & ) ), SLOT( slotFileMoved( const QString &, const QString & ) ) ); connect( CollectionDB::instance(), SIGNAL( fileMoved( const QString &, const QString &, const QString & ) ), SLOT( slotFileMoved( const QString &, const QString & ) ) ); connect( CollectionDB::instance(), SIGNAL( fileDeleted( const QString & ) ), SLOT( slotFileDeleted( const QString & ) ) ); connect( CollectionDB::instance(), SIGNAL( fileDeleted( const QString &, const QString & ) ), SLOT( slotFileDeleted( const QString & ) ) );}// Queue a job, but not before checking if the moodbar is enabled// in the config, if the moodbar analyzer appears to be working,// and if a job for that URL isn't already queued. Returns true// if the job is already running, false otherwise.boolMoodServer::queueJob( MetaBundle *bundle ){ if( m_moodbarBroken || !AmarokConfig::showMoodbar() ) return false;
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?