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 + -
显示快捷键?