|
|
|
/***************************************************************************
|
|
|
|
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
|
|
|
|
// TQPixmap, 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(), TQT_SIGNAL( jobEvent( int ) ),
|
|
|
|
// TQT_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 )
|
|
|
|
// {
|
|
|
|
// TQPixmap 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 TQObject::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 TQPixmap. 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 <tqfile.h>
|
|
|
|
#include <tqdir.h> // For TQDir::rename()
|
|
|
|
#include <tqpainter.h>
|
|
|
|
#include <tqtimer.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(), TQT_SIGNAL( moodbarPrefs( bool, bool, int, bool ) ),
|
|
|
|
TQT_SLOT( slotMoodbarPrefs( bool, bool, int, bool ) ) );
|
|
|
|
connect( CollectionDB::instance(),
|
|
|
|
TQT_SIGNAL( fileMoved( const TQString &, const TQString & ) ),
|
|
|
|
TQT_SLOT( slotFileMoved( const TQString &, const TQString & ) ) );
|
|
|
|
connect( CollectionDB::instance(),
|
|
|
|
TQT_SIGNAL( fileMoved( const TQString &, const TQString &, const TQString & ) ),
|
|
|
|
TQT_SLOT( slotFileMoved( const TQString &, const TQString & ) ) );
|
|
|
|
connect( CollectionDB::instance(),
|
|
|
|
TQT_SIGNAL( fileDeleted( const TQString & ) ),
|
|
|
|
TQT_SLOT( slotFileDeleted( const TQString & ) ) );
|
|
|
|
connect( CollectionDB::instance(),
|
|
|
|
TQT_SIGNAL( fileDeleted( const TQString &, const TQString & ) ),
|
|
|
|
TQT_SLOT( slotFileDeleted( const TQString & ) ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
bool
|
|
|
|
MoodServer::queueJob( MetaBundle *bundle )
|
|
|
|
{
|
|
|
|
if( m_moodbarBroken || !AmarokConfig::showMoodbar() )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
m_mutex.lock();
|
|
|
|
|
|
|
|
// Check if the currently running job is for that URL
|
|
|
|
if( m_currentProcess != 0 &&
|
|
|
|
m_currentData.m_url == bundle->url() )
|
|
|
|
{
|
|
|
|
debug() << "MoodServer::queueJob: Not re-queueing already-running job "
|
|
|
|
<< bundle->url().path() << endl;
|
|
|
|
m_mutex.unlock();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if there's already a job in the queue for that URL
|
|
|
|
TQValueList<ProcData>::iterator it;
|
|
|
|
for( it = m_jobQueue.begin(); it != m_jobQueue.end(); ++it )
|
|
|
|
{
|
|
|
|
if( (*it).m_url == bundle->url() )
|
|
|
|
{
|
|
|
|
(*it).m_refcount++;
|
|
|
|
debug() << "MoodServer::queueJob: Job for " << bundle->url().path()
|
|
|
|
<< " already in queue, increasing refcount to "
|
|
|
|
<< (*it).m_refcount << endl;
|
|
|
|
m_mutex.unlock();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_jobQueue.append( ProcData( bundle->url(),
|
|
|
|
bundle->url().path(),
|
|
|
|
bundle->moodbar().moodFilename( bundle->url() ) ) );
|
|
|
|
|
|
|
|
debug() << "MoodServer::queueJob: Queued job for " << bundle->url().path()
|
|
|
|
<< ", " << m_jobQueue.size() << " jobs in queue." << endl;
|
|
|
|
|
|
|
|
m_mutex.unlock();
|
|
|
|
|
|
|
|
// New jobs *must* be started from the GUI thread!
|
|
|
|
TQTimer::singleShot( 1000, this, TQT_SLOT( slotNewJob( void ) ) );
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Decrements the refcount of the job for the given URL
|
|
|
|
// and deletes that job if necessary.
|
|
|
|
void
|
|
|
|
MoodServer::deQueueJob( KURL url )
|
|
|
|
{
|
|
|
|
m_mutex.lock();
|
|
|
|
|
|
|
|
// Can't de-queue running jobs
|
|
|
|
if( m_currentProcess != 0 &&
|
|
|
|
m_currentData.m_url == url )
|
|
|
|
{
|
|
|
|
debug() << "MoodServer::deQueueJob: Not de-queueing already-running job "
|
|
|
|
<< url.path() << endl;
|
|
|
|
m_mutex.unlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if there's already a job in the queue for that URL
|
|
|
|
TQValueList<ProcData>::iterator it;
|
|
|
|
for( it = m_jobQueue.begin(); it != m_jobQueue.end(); ++it )
|
|
|
|
{
|
|
|
|
if( (*it).m_url == url )
|
|
|
|
{
|
|
|
|
(*it).m_refcount--;
|
|
|
|
|
|
|
|
if( (*it).m_refcount == 0 )
|
|
|
|
{
|
|
|
|
debug() << "MoodServer::deQueueJob: nobody cares about "
|
|
|
|
<< (*it).m_url.path()
|
|
|
|
<< " anymore, deleting from queue" << endl;
|
|
|
|
m_jobQueue.erase( it );
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
debug() << "MoodServer::deQueueJob: decrementing refcount of "
|
|
|
|
<< (*it).m_url.path() << " to " << (*it).m_refcount
|
|
|
|
<< endl;
|
|
|
|
|
|
|
|
m_mutex.unlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
debug() << "MoodServer::deQueueJob: tried to delete nonexistent job "
|
|
|
|
<< url.path() << endl;
|
|
|
|
|
|
|
|
m_mutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This slot exists so that jobs can be started from the GUI thread,
|
|
|
|
// just in case queueJob() is run from another thread. Only run
|
|
|
|
// directly if you're in the GUI thread!
|
|
|
|
void
|
|
|
|
MoodServer::slotNewJob( void )
|
|
|
|
{
|
|
|
|
if( m_moodbarBroken )
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_mutex.lock();
|
|
|
|
|
|
|
|
// Are we already running a process?
|
|
|
|
if( m_jobQueue.isEmpty() || m_currentProcess != 0 )
|
|
|
|
{
|
|
|
|
m_mutex.unlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_currentData = m_jobQueue.first();
|
|
|
|
m_jobQueue.pop_front();
|
|
|
|
|
|
|
|
debug() << "MoodServer::slotNewJob: starting new analyzer process: "
|
|
|
|
<< "moodbar -o " << m_currentData.m_outfile << ".tmp "
|
|
|
|
<< m_currentData.m_infile << endl;
|
|
|
|
debug() << "MoodServer::slotNewJob: " << m_jobQueue.size()
|
|
|
|
<< " jobs left in queue." << endl;
|
|
|
|
|
|
|
|
|
|
|
|
// Write to outfile.mood.tmp so that new Moodbar instances
|
|
|
|
// don't think the mood data exists while the analyzer is
|
|
|
|
// running. Then rename the file later.
|
|
|
|
m_currentProcess = new Amarok::Process( this );
|
|
|
|
m_currentProcess->setPriority( 18 ); // Nice the process
|
|
|
|
*m_currentProcess << KStandardDirs::findExe( "moodbar" ) << "-o"
|
|
|
|
<< (m_currentData.m_outfile + ".tmp")
|
|
|
|
<< m_currentData.m_infile;
|
|
|
|
|
|
|
|
connect( m_currentProcess, TQT_SIGNAL( processExited( KProcess* ) ),
|
|
|
|
TQT_SLOT( slotJobCompleted( KProcess* ) ) );
|
|
|
|
|
|
|
|
// We have to enable KProcess::Stdout (even though we don't monitor
|
|
|
|
// it) since otherwise the child process crashes every time in
|
|
|
|
// KProcess::start() (but only when started from the loader!). I
|
|
|
|
// have no idea why, but I imagine it's a bug in KDE.
|
|
|
|
if( !m_currentProcess->start( KProcess::NotifyOnExit, KProcess::AllOutput ) )
|
|
|
|
{
|
|
|
|
// If we have an error starting the process, it's never
|
|
|
|
// going to work, so call moodbarBroken()
|
|
|
|
warning() << "Can't start moodbar analyzer process!" << endl;
|
|
|
|
delete m_currentProcess;
|
|
|
|
m_currentProcess = 0;
|
|
|
|
m_mutex.unlock();
|
|
|
|
setMoodbarBroken();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extreme reentrancy pedatry :)
|
|
|
|
KURL url = m_currentData.m_url;
|
|
|
|
m_mutex.unlock();
|
|
|
|
|
|
|
|
emit jobEvent( url, Moodbar::JobStateRunning );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This always run in the GUI thread. It is called
|
|
|
|
// when an analyzer process terminates
|
|
|
|
void
|
|
|
|
MoodServer::slotJobCompleted( KProcess *proc )
|
|
|
|
{
|
|
|
|
m_mutex.lock();
|
|
|
|
|
|
|
|
// Pedantry
|
|
|
|
if( proc != m_currentProcess )
|
|
|
|
warning() << "MoodServer::slotJobCompleted: proc != m_currentProcess!" << endl;
|
|
|
|
|
|
|
|
ReturnStatus returnval;
|
|
|
|
if( !m_currentProcess->normalExit() )
|
|
|
|
returnval = Crash;
|
|
|
|
else
|
|
|
|
returnval = (ReturnStatus) m_currentProcess->exitStatus();
|
|
|
|
|
|
|
|
bool success = (returnval == Success);
|
|
|
|
KURL url = m_currentData.m_url;
|
|
|
|
|
|
|
|
if( success )
|
|
|
|
{
|
|
|
|
TQString file = m_currentData.m_outfile;
|
|
|
|
TQString dir = file.left( file.findRev( '/' ) );
|
|
|
|
file = file.right( file.length() - file.findRev( '/' ) - 1 );
|
|
|
|
TQDir( dir ).rename( file + ".tmp", file );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
TQFile::remove( m_currentData.m_outfile + ".tmp" );
|
|
|
|
|
|
|
|
delete m_currentProcess;
|
|
|
|
m_currentProcess = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// If the moodbar was disabled, we killed the process
|
|
|
|
if( !AmarokConfig::showMoodbar() )
|
|
|
|
{
|
|
|
|
debug() << "MoodServer::slotJobCompleted: moodbar disabled, job killed" << endl;
|
|
|
|
m_mutex.unlock();
|
|
|
|
emit jobEvent( url, Moodbar::JobStateFailed );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch( returnval )
|
|
|
|
{
|
|
|
|
case Success:
|
|
|
|
debug() << "MoodServer::slotJobCompleted: job completed successfully" << endl;
|
|
|
|
m_mutex.unlock();
|
|
|
|
slotNewJob();
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Crash and NoFile don't mean that moodbar is broken.
|
|
|
|
// Something bad happened, but it's probably a problem with this file
|
|
|
|
// Just log an error message and emit jobEvent().
|
|
|
|
case Crash:
|
|
|
|
debug() << "MoodServer::slotJobCompleted: moodbar crashed on "
|
|
|
|
<< m_currentData.m_infile << endl;
|
|
|
|
m_mutex.unlock();
|
|
|
|
slotNewJob();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NoFile:
|
|
|
|
debug() << "MoodServer::slotJobCompleted: moodbar had a problem with "
|
|
|
|
<< m_currentData.m_infile << endl;
|
|
|
|
m_mutex.unlock();
|
|
|
|
slotNewJob();
|
|
|
|
break;
|
|
|
|
|
|
|
|
// NoPlugin and CommandLine mean the moodbar is broken
|
|
|
|
// The moodbar analyzer is not likely to work ever, so let the
|
|
|
|
// user know about it and disable new jobs.
|
|
|
|
default:
|
|
|
|
m_mutex.unlock();
|
|
|
|
setMoodbarBroken();
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
emit jobEvent( url, success ? Moodbar::JobStateSucceeded
|
|
|
|
: Moodbar::JobStateFailed );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This is called whenever "Ok" or "Apply" is pressed on the configuration
|
|
|
|
// dialog. If the moodbar is disabled, kill the current process and
|
|
|
|
// clear the queue
|
|
|
|
void
|
|
|
|
MoodServer::slotMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic )
|
|
|
|
{
|
|
|
|
if( show == true)
|
|
|
|
return;
|
|
|
|
|
|
|
|
(void) moodier; (void) alter; (void) withMusic;
|
|
|
|
|
|
|
|
// If we have a current process, kill it. Cleanup happens in
|
|
|
|
// slotJobCompleted() above. We do *not* want to lock the
|
|
|
|
// mutex when calling this!
|
|
|
|
if( m_currentProcess != 0 )
|
|
|
|
m_currentProcess->kill();
|
|
|
|
|
|
|
|
clearJobs();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// When a file is deleted, either manually using Organize Collection or
|
|
|
|
// automatically detected using AFT, delete the corresponding mood file.
|
|
|
|
void
|
|
|
|
MoodServer::slotFileDeleted( const TQString &path )
|
|
|
|
{
|
|
|
|
TQString mood = Moodbar::moodFilename( KURL::fromPathOrURL( path ) );
|
|
|
|
if( mood.isEmpty() || !TQFile::exists( mood ) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
debug() << "MoodServer::slotFileDeleted: deleting " << mood << endl;
|
|
|
|
TQFile::remove( mood );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// When a file is moved, either manually using Organize Collection or
|
|
|
|
// automatically using AFT, move the corresponding mood file.
|
|
|
|
void
|
|
|
|
MoodServer::slotFileMoved( const TQString &srcPath, const TQString &dstPath )
|
|
|
|
{
|
|
|
|
TQString srcMood = Moodbar::moodFilename( KURL::fromPathOrURL( srcPath ) );
|
|
|
|
TQString dstMood = Moodbar::moodFilename( KURL::fromPathOrURL( dstPath ) );
|
|
|
|
|
|
|
|
if( srcMood.isEmpty() || dstMood.isEmpty() ||
|
|
|
|
srcMood == dstMood || !TQFile::exists( srcMood ) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
debug() << "MoodServer::slotFileMoved: moving " << srcMood << " to "
|
|
|
|
<< dstMood << endl;
|
|
|
|
|
|
|
|
Moodbar::copyFile( srcMood, dstMood );
|
|
|
|
TQFile::remove( srcMood );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This is called when we decide that the moodbar analyzer is
|
|
|
|
// never going to work. Disable further jobs, and let the user
|
|
|
|
// know about it. This should only be called when m_currentProcess == 0.
|
|
|
|
void
|
|
|
|
MoodServer::setMoodbarBroken( void )
|
|
|
|
{
|
|
|
|
warning() << "Uh oh, it looks like the moodbar analyzer is not going to work"
|
|
|
|
<< endl;
|
|
|
|
|
|
|
|
Amarok::StatusBar::instance()->longMessage( i18n(
|
|
|
|
"The Amarok moodbar analyzer program seems to be broken. "
|
|
|
|
"This is probably because the moodbar package is not installed "
|
|
|
|
"correctly. The moodbar package, installation instructions, and "
|
|
|
|
"troubleshooting help can be found on the wiki page at <a href='"
|
|
|
|
WEBPAGE "'>" WEBPAGE "</a>. "
|
|
|
|
"When the problem is fixed, please restart Amarok."),
|
|
|
|
KDE::StatusBar::Error );
|
|
|
|
|
|
|
|
|
|
|
|
m_moodbarBroken = true;
|
|
|
|
clearJobs();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Clear the job list and emit signals
|
|
|
|
void
|
|
|
|
MoodServer::clearJobs( void )
|
|
|
|
{
|
|
|
|
// We don't want to emit jobEvent (or really do anything
|
|
|
|
// external) while the mutex is locked.
|
|
|
|
m_mutex.lock();
|
|
|
|
TQValueList<ProcData> queueCopy
|
|
|
|
= TQDeepCopy< TQValueList<ProcData> > ( m_jobQueue );
|
|
|
|
m_jobQueue.clear();
|
|
|
|
m_mutex.unlock();
|
|
|
|
|
|
|
|
TQValueList<ProcData>::iterator it;
|
|
|
|
for( it = queueCopy.begin(); it != queueCopy.end(); ++it )
|
|
|
|
emit jobEvent( (*it).m_url, Moodbar::JobStateFailed );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Moodbar class
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
// The moodbar behavior is nearly identical in the JobQueued and
|
|
|
|
// JobRunning states, but we have to keep track anyway so the
|
|
|
|
// PlaylistItem knows what do display
|
|
|
|
|
|
|
|
#define JOB_PENDING(state) ((state)==JobQueued||(state)==JobRunning)
|
|
|
|
|
|
|
|
|
|
|
|
// The passed MetaBundle _must_ be non-NULL, and the pointer must be valid
|
|
|
|
// as long as this instance is alive. The Moodbar is only meant to be a
|
|
|
|
// member of a MetaBundle, in other words.
|
|
|
|
|
|
|
|
Moodbar::Moodbar( MetaBundle *mb )
|
|
|
|
: TQObject ( )
|
|
|
|
, m_bundle ( mb )
|
|
|
|
, m_hueSort ( 0 )
|
|
|
|
, m_state ( Unloaded )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If we have any pending jobs, de-queue them. The use case I
|
|
|
|
// have in mind is if the user has the moodbar column displayed
|
|
|
|
// and adds all his/her tracks to the playlist, then deletes
|
|
|
|
// them again.
|
|
|
|
Moodbar::~Moodbar( void )
|
|
|
|
{
|
|
|
|
if( JOB_PENDING( m_state ) )
|
|
|
|
MoodServer::instance()->deQueueJob( m_url );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MetaBundle's are often assigned using operator=, so so are we.
|
|
|
|
Moodbar&
|
|
|
|
Moodbar::operator=( const Moodbar &mood )
|
|
|
|
{
|
|
|
|
// Need to check this before locking both!
|
|
|
|
if( &mood == this )
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
m_mutex.lock();
|
|
|
|
mood.m_mutex.lock();
|
|
|
|
|
|
|
|
State oldState = m_state;
|
|
|
|
KURL oldURL = m_url;
|
|
|
|
|
|
|
|
m_data = mood.m_data;
|
|
|
|
m_pixmap = mood.m_pixmap;
|
|
|
|
m_state = mood.m_state;
|
|
|
|
m_url = mood.m_url;
|
|
|
|
// DO NOT overwrite m_bundle! That should never change.
|
|
|
|
|
|
|
|
// Signal connections and job queues are part of our "state",
|
|
|
|
// so those should be updated too.
|
|
|
|
if( JOB_PENDING( m_state ) && !JOB_PENDING( oldState ) )
|
|
|
|
{
|
|
|
|
connect( MoodServer::instance(),
|
|
|
|
TQT_SIGNAL( jobEvent( KURL, int ) ),
|
|
|
|
TQT_SLOT( slotJobEvent( KURL, int ) ) );
|
|
|
|
// Increase the refcount for this job. Use mood.m_bundle
|
|
|
|
// since that one's already initialized.
|
|
|
|
MoodServer::instance()->queueJob( mood.m_bundle );
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we had a job pending, de-queue it
|
|
|
|
if( !JOB_PENDING( m_state ) && JOB_PENDING( oldState ) )
|
|
|
|
{
|
|
|
|
MoodServer::instance()->disconnect( this, TQT_SLOT( slotJobEvent( KURL, int ) ) );
|
|
|
|
MoodServer::instance()->deQueueJob( oldURL );
|
|
|
|
}
|
|
|
|
|
|
|
|
mood.m_mutex.unlock();
|
|
|
|
m_mutex.unlock();
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Reset the moodbar to its Unloaded state. This is useful when
|
|
|
|
// the configuration is changed, and all the moodbars need to be
|
|
|
|
// reloaded.
|
|
|
|
void
|
|
|
|
Moodbar::reset( void )
|
|
|
|
{
|
|
|
|
m_mutex.lock();
|
|
|
|
|
|
|
|
debug() << "Resetting moodbar: " << m_bundle->url().path() << endl;
|
|
|
|
|
|
|
|
if( JOB_PENDING( m_state ) )
|
|
|
|
{
|
|
|
|
MoodServer::instance()->disconnect( this, TQT_SLOT( slotJobEvent( KURL, int ) ) );
|
|
|
|
MoodServer::instance()->deQueueJob( m_url );
|
|
|
|
}
|
|
|
|
|
|
|
|
m_data.clear();
|
|
|
|
m_pixmap = TQPixmap();
|
|
|
|
m_url = KURL();
|
|
|
|
m_hueSort = 0;
|
|
|
|
m_state = Unloaded;
|
|
|
|
|
|
|
|
m_mutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Make a copy of all of our implicitly shared data
|
|
|
|
void
|
|
|
|
Moodbar::detach( void )
|
|
|
|
{
|
|
|
|
m_mutex.lock();
|
|
|
|
|
|
|
|
m_data = TQDeepCopy<ColorList>(m_data);
|
|
|
|
m_pixmap.detach();
|
|
|
|
|
|
|
|
// Apparently this is the wrong hack -- don't detach urls
|
|
|
|
//TQString url( TQDeepCopy<TQString>( m_url.url() ) );
|
|
|
|
//m_url = KURL::fromPathOrURL( url );
|
|
|
|
|
|
|
|
m_mutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If possible, try to open the bundle's .mood file. When this method
|
|
|
|
// returns true, this instance must be able to draw(). This may
|
|
|
|
// change the state to CantLoad, but usually leaves the state
|
|
|
|
// untouched.
|
|
|
|
bool
|
|
|
|
Moodbar::dataExists( void )
|
|
|
|
{
|
|
|
|
// Put this first for efficiency
|
|
|
|
if( m_state == Loaded )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Should we bother checking for the file?
|
|
|
|
if( m_state == CantLoad ||
|
|
|
|
JOB_PENDING( m_state ) ||
|
|
|
|
m_state == JobFailed ||
|
|
|
|
!canHaveMood() )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
m_mutex.lock();
|
|
|
|
bool res = readFile();
|
|
|
|
m_mutex.unlock();
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If m_bundle is not a local file or for some other reason cannot
|
|
|
|
// have mood data, return false, and set the state to CantLoad to
|
|
|
|
// save future checks. Note that MoodServer::m_moodbarBroken == true
|
|
|
|
// does not mean we can't have a mood file; it just means that we
|
|
|
|
// can't generate new ones.
|
|
|
|
bool
|
|
|
|
Moodbar::canHaveMood( void )
|
|
|
|
{
|
|
|
|
if( m_state == CantLoad )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Don't try to analyze it if we can't even determine it has a length
|
|
|
|
// If for some reason we can't determine a file name, give up
|
|
|
|
// If the moodbar is disabled, set to CantLoad -- if the user re-enables
|
|
|
|
// the moodbar, we'll be reset() anyway.
|
|
|
|
if( !AmarokConfig::showMoodbar() ||
|
|
|
|
!m_bundle->url().isLocalFile() ||
|
|
|
|
!m_bundle->length() ||
|
|
|
|
moodFilename( m_bundle->url() ).isEmpty() )
|
|
|
|
{
|
|
|
|
m_state = CantLoad;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ask MoodServer to queue an analyzer job for us if necessary. This
|
|
|
|
// method will only do something the first time it's called, as it's
|
|
|
|
// guaranteed to change the state from Unloaded.
|
|
|
|
void
|
|
|
|
Moodbar::load( void )
|
|
|
|
{
|
|
|
|
if( m_state != Unloaded )
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_mutex.lock();
|
|
|
|
|
|
|
|
if( !canHaveMood() )
|
|
|
|
{
|
|
|
|
// State is now CantLoad
|
|
|
|
m_mutex.unlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( readFile() )
|
|
|
|
{
|
|
|
|
// State is now Loaded
|
|
|
|
m_mutex.unlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( MoodServer::instance()->moodbarBroken() )
|
|
|
|
{
|
|
|
|
m_state = JobFailed;
|
|
|
|
m_mutex.unlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ok no more excuses, we have to queue a job
|
|
|
|
connect( MoodServer::instance(),
|
|
|
|
TQT_SIGNAL( jobEvent( KURL, int ) ),
|
|
|
|
TQT_SLOT( slotJobEvent( KURL, int ) ) );
|
|
|
|
bool isRunning = MoodServer::instance()->queueJob( m_bundle );
|
|
|
|
m_state = isRunning ? JobRunning : JobQueued;
|
|
|
|
m_url = m_bundle->url(); // Use this URL for MoodServer::deQueueJob
|
|
|
|
|
|
|
|
m_mutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This is called by MoodServer when our moodbar analyzer job starts
|
|
|
|
// or finishes. It may change the state from JobQueued / JobRunning
|
|
|
|
// to JobRunning, Loaded, or JobFailed. It may emit a jobEvent()
|
|
|
|
void
|
|
|
|
Moodbar::slotJobEvent( KURL url, int newState )
|
|
|
|
{
|
|
|
|
// Is this job for us?
|
|
|
|
if( !JOB_PENDING( m_state ) || url != m_bundle->url() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool success = ( newState == JobStateSucceeded );
|
|
|
|
|
|
|
|
// We don't really care about this, but our listeners might
|
|
|
|
if( newState == JobStateRunning )
|
|
|
|
{
|
|
|
|
m_state = JobRunning;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_mutex.lock();
|
|
|
|
|
|
|
|
// Disconnect the signal for efficiency's sake
|
|
|
|
MoodServer::instance()->disconnect( this, TQT_SLOT( slotJobEvent( KURL, int ) ) );
|
|
|
|
|
|
|
|
if( !success )
|
|
|
|
{
|
|
|
|
m_state = JobFailed;
|
|
|
|
m_mutex.unlock();
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( readFile() )
|
|
|
|
{
|
|
|
|
// m_state is now Loaded
|
|
|
|
m_mutex.unlock();
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we get here it means the analyzer job went wrong, but
|
|
|
|
// somehow the MoodServer didn't know about it
|
|
|
|
debug() << "WARNING: Failed to open file " << moodFilename( m_bundle->url() )
|
|
|
|
<< " -- something is very wrong" << endl;
|
|
|
|
m_state = JobFailed;
|
|
|
|
m_mutex.unlock();
|
|
|
|
|
|
|
|
out:
|
|
|
|
emit jobEvent( newState );
|
|
|
|
// This is a cheat for PlaylistItem so it doesn't have to
|
|
|
|
// use signals
|
|
|
|
m_bundle->moodbarJobEvent( newState );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Draw the moodbar onto a pixmap of the given dimensions and return
|
|
|
|
// it. This is mostly Gav's original code, cut and pasted from
|
|
|
|
// various places. This will not change the state.
|
|
|
|
TQPixmap
|
|
|
|
Moodbar::draw( int width, int height )
|
|
|
|
{
|
|
|
|
if( m_state != Loaded || !AmarokConfig::showMoodbar() ) // Naughty caller!
|
|
|
|
return TQPixmap();
|
|
|
|
|
|
|
|
m_mutex.lock();
|
|
|
|
|
|
|
|
// Do we have to repaint, or can we use the cache?
|
|
|
|
if( m_pixmap.width() == width && m_pixmap.height() == height )
|
|
|
|
{
|
|
|
|
m_mutex.unlock();
|
|
|
|
return m_pixmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_pixmap = TQPixmap( width, height );
|
|
|
|
TQPainter paint( &m_pixmap );
|
|
|
|
|
|
|
|
// First average the moodbar samples that will go into each
|
|
|
|
// vertical bar on the screen.
|
|
|
|
|
|
|
|
if( m_data.size() == 0 ) // Play it safe -- see below
|
|
|
|
return TQPixmap();
|
|
|
|
|
|
|
|
ColorList screenColors;
|
|
|
|
TQColor bar;
|
|
|
|
float r, g, b;
|
|
|
|
int h, s, v;
|
|
|
|
|
|
|
|
for( int i = 0; i < width; i++ )
|
|
|
|
{
|
|
|
|
r = 0.f; g = 0.f; b = 0.f;
|
|
|
|
|
|
|
|
// m_data.size() needs to be at least 1 for this not to crash!
|
|
|
|
uint start = i * m_data.size() / width;
|
|
|
|
uint end = (i + 1) * m_data.size() / width;
|
|
|
|
if( start == end )
|
|
|
|
end = start + 1;
|
|
|
|
|
|
|
|
for( uint j = start; j < end; j++ )
|
|
|
|
{
|
|
|
|
r += m_data[j].red();
|
|
|
|
g += m_data[j].green();
|
|
|
|
b += m_data[j].blue();
|
|
|
|
}
|
|
|
|
|
|
|
|
uint n = end - start;
|
|
|
|
bar = TQColor( int( r / float( n ) ),
|
|
|
|
int( g / float( n ) ),
|
|
|
|
int( b / float( n ) ), TQColor::Rgb );
|
|
|
|
|
|
|
|
/* Snap to the HSV values for later */
|
|
|
|
bar.getHsv(&h, &s, &v);
|
|
|
|
bar.setHsv(h, s, v);
|
|
|
|
|
|
|
|
screenColors.push_back( bar );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Paint the bars. This is Gav's painting code -- it breaks up the
|
|
|
|
// monotony of solid-color vertical bars by playing with the saturation
|
|
|
|
// and value.
|
|
|
|
|
|
|
|
for( int x = 0; x < width; x++ )
|
|
|
|
{
|
|
|
|
screenColors[x].getHsv( &h, &s, &v );
|
|
|
|
|
|
|
|
for( int y = 0; y <= height / 2; y++ )
|
|
|
|
{
|
|
|
|
float coeff = float(y) / float(height / 2);
|
|
|
|
float coeff2 = 1.f - ((1.f - coeff) * (1.f - coeff));
|
|
|
|
coeff = 1.f - (1.f - coeff) / 2.f;
|
|
|
|
coeff2 = 1.f - (1.f - coeff2) / 2.f;
|
|
|
|
paint.setPen( TQColor( h,
|
|
|
|
CLAMP( 0, int( float( s ) * coeff ), 255 ),
|
|
|
|
CLAMP( 0, int( 255.f - (255.f - float( v )) * coeff2), 255 ),
|
|
|
|
TQColor::Hsv ) );
|
|
|
|
paint.drawPoint(x, y);
|
|
|
|
paint.drawPoint(x, height - 1 - y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_mutex.unlock();
|
|
|
|
|
|
|
|
return m_pixmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define NUM_HUES 12
|
|
|
|
|
|
|
|
// Read the .mood file. Returns true if the read was successful
|
|
|
|
// and changes the state to Loaded; returns false and leaves the
|
|
|
|
// state untouched otherwise.
|
|
|
|
//
|
|
|
|
// This is based on Gav's original code. We do the mood altering
|
|
|
|
// (AmarokConfig::AlterMood()) here, as well as calculating the
|
|
|
|
// hue-based sort. All displayed moodbars will be reset() when
|
|
|
|
// the config is changed, so there's no harm in doing it here.
|
|
|
|
//
|
|
|
|
// This method must be called with the instance locked.
|
|
|
|
bool
|
|
|
|
Moodbar::readFile( void )
|
|
|
|
{
|
|
|
|
if( !AmarokConfig::showMoodbar() )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if( m_state == Loaded )
|
|
|
|
return true;
|
|
|
|
|
|
|