~ubuntu-branches/ubuntu/gutsy/amarok/gutsy-updates

« back to all changes in this revision

Viewing changes to amarok/src/moodbar.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Anthony Mercatante
  • Date: 2006-11-03 23:57:33 UTC
  • mfrom: (1.31.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061103235733-a41oyfz4mzienqin
Tags: 2:1.4.4-0ubuntu2
Add debian/kubuntu-media-amarok and
debian amarok_play_audiocd.desktop for good KDE integration

Show diffs side-by-side

added added

removed removed

Lines of Context:
39
39
//    void MyClass::MyClass( void )
40
40
//    {
41
41
//        // This only needs to be done once!
42
 
//        connect( &m_bundle.moodbar(), SIGNAL( jobEvent( int ) ), 
 
42
//        connect( &m_bundle.moodbar(), SIGNAL( jobEvent( int ) ),
43
43
//                 SLOT( newMoodData( int ) ) );
44
44
//    }
45
45
//
73
73
//    Moodbar.  The Moodbar emits this signal when an analyzer process
74
74
//    has started or completed and it has loaded its moodbar data.
75
75
//    (This connection will exist for the lifetime of the instance of
76
 
//    MyClass and hence only needs to be created once.)  
 
76
//    MyClass and hence only needs to be created once.)
77
77
//
78
78
//  * Whenever the MetaBundle associated with this instance of MyClass
79
79
//    is changed, so does the moodbar, so we should reload it.  The
98
98
// Implementation
99
99
// --------------
100
100
//
101
 
// There are two new classes, namely the Moodbar (a member of 
 
101
// There are two new classes, namely the Moodbar (a member of
102
102
// MetaBundle), and the MoodServer.  The former is the only public
103
103
// class.  In a nutshell, the Moodbar is responsible for reading
104
 
// and drawing mood data, and the MoodServer is in charge of 
 
104
// and drawing mood data, and the MoodServer is in charge of
105
105
// queueing analyzer jobs and notifying interested Moodbar's when
106
106
// their job is done.
107
107
 
125
125
// not reentrant (from what I understand), so we don't want that being
126
126
// called every time a Moodbar is destroyed!  For the same reason, the
127
127
// PlaylistItem does not listen for the jobEvent() signal; instead it
128
 
// reimplements the MetaBundle::moodbarJobEvent() virtual method.  
 
128
// reimplements the MetaBundle::moodbarJobEvent() virtual method.
129
129
//
130
130
// Again for this reason, the individual Moodbar's don't listen for
131
131
// the App::moodbarPrefs() signal (which is emitted every time the
138
138
// A moodbar is always in one of the following states:
139
139
//
140
140
//   Unloaded:   A newly-created (or newly reset()) Moodbar is in this
141
 
//               state.  The Moodbar remains in this state until 
 
141
//               state.  The Moodbar remains in this state until
142
142
//               dataExists() or load() is called.  Note that load()
143
143
//               will return immediately unless the state is Unloaded.
144
144
//   CantLoad:   For some reason we know that we'll never be able to
147
147
//               immediately in this state.
148
148
//   JobQueued:  At some point load() was called, so we queued a job with
149
149
//               the MoodServer which hasn't started yet.  In this state,
150
 
//               ~Moodbar(), reset(), etc. knows to dequeue jobs and 
 
150
//               ~Moodbar(), reset(), etc. knows to dequeue jobs and
151
151
//               disconnect signals.
152
152
//   JobRunning: Our analyzer job is actually running.  The moodbar behaves
153
153
//               basically the same as in the JobQueued state; this state
156
156
//               trying), and came up empty.  This state behaves basically
157
157
//               the same as CantLoad.
158
158
//   Loaded:     This is the only state in which draw() will work.
159
 
//   
 
159
//
160
160
//
161
161
// Note that nothing is done to load until dataExists() is called; this
162
162
// is because there may very well be MetaBundle's floating around that
163
163
// aren't displayed in the GUI.
164
 
// 
 
164
//
165
165
// Important members:
166
166
//   m_bundle: link to the parent bundle
167
167
//   m_data:   if we are loaded, this is the contents of the .mood file
168
 
//   m_pixmap: the last time draw() was called, we cached what we drew 
 
168
//   m_pixmap: the last time draw() was called, we cached what we drew
169
169
//             here
170
170
//   m_url:    cache the URL of our queued job for de-queueing
171
171
//   m_state:  our current state
185
185
//       ask MoodServer to run a job for us.  Always changes the state
186
186
//       from Unloaded so subsequent calls to load() do nothing.
187
187
//
188
 
//   draw(): Draw the moodbar onto a QPixmap.  Cache what we drew 
 
188
//   draw(): Draw the moodbar onto a QPixmap.  Cache what we drew
189
189
//       so that if draw() is called again with the same dimensions
190
190
//       we don't have to redraw.
191
191
//
197
197
//       signal.
198
198
//
199
199
//   (private) readFile(): When we think there's a file available, this
200
 
//       method tries to load it.  We also do the display-independent 
 
200
//       method tries to load it.  We also do the display-independent
201
201
//       analysis here, namely, calculating the sorting index (for sort-
202
202
//       by-hue in the Playlist), and Making Moodier.
203
203
 
204
204
 
205
205
// The MoodServer class --
206
 
// 
 
206
//
207
207
// This is a singleton class.  It is responsible for queueing analyzer
208
208
// jobs requested by Moodbar's, running them, and notifying the
209
209
// Moodbar's when the job has started and completed, successful or no.
218
218
// instead, each queued job has a refcount, which is increased.  This
219
219
// is to support the de-queueing of jobs when Moodbar's are destroyed;
220
220
// the use case I have in mind is if the user has the moodbar column
221
 
// displayed in the playlist, he/she adds 1000 tracks to the playlist 
222
 
// (at which point all the displayed tracks queue moodbar jobs), and 
223
 
// then decides to clear the playlist again.  The jobEvent() signal 
 
221
// displayed in the playlist, he/she adds 1000 tracks to the playlist
 
222
// (at which point all the displayed tracks queue moodbar jobs), and
 
223
// then decides to clear the playlist again.  The jobEvent() signal
224
224
// passes the URL of the job that was completed.
225
225
//
226
226
// The analyzer is actually run using a KProcess.  ThreadWeaver::Job
227
227
// is not a good solution, since we need more flexibility in the
228
228
// queuing process, and in addition, KProcess'es must be started from
229
 
// the GUI thread!  
230
 
// 
 
229
// the GUI thread!
 
230
//
231
231
// Important members:
232
232
//   m_jobQueue:       this is a list of MoodServer::ProcData structures,
233
233
//                     which contain the data needed to start and reference
234
234
//                     a process, as well as a refcount.
235
235
//   m_currentProcess: the currently-running KProcess, if any.
236
 
//   m_currentData:    the ProcData structure for the currently-running 
 
236
//   m_currentData:    the ProcData structure for the currently-running
237
237
//                     process.
238
238
//   m_moodbarBroken:  this is set when there's an error running the analyzer
239
239
//                     that indicates the analyzer will never be able to run.
245
245
// Important methods:
246
246
//
247
247
//   queueJob(): Add a job to the queue.  If the job is being run, do nothing;
248
 
//       if the job is already queued, increase its refcount, and if 
 
248
//       if the job is already queued, increase its refcount, and if
249
249
//       m_moodbarBroken == true, do nothing.
250
250
//
251
251
//   deQueueJob(): Called from ~Moodbar(), for instance.  Decreases
255
255
//   (private slot) slotJobCompleted(): Called when a job finishes.  Do some
256
256
//       cleanup, and notify the interested parties.  Set m_moodbarBroken if
257
257
//       necessary; otherwise call slotNewJob().
258
 
//       
 
258
//
259
259
//   (private slot) slotNewJob(): Called by slotJobCompleted() and queueJob().
260
260
//       Take a job off the queue and start the KProcess.
261
261
//
262
262
//   (private slot) slotMoodbarPrefs(): Called when the Amarok config changes.
263
263
//       If the moodbar has been disabled completely, kill the current job
264
264
//       (if any), clear the queue, and notify the interested Moodbar's.
 
265
//
 
266
//   (private slot) slotFileDeleted(): Called when a music file is deleted, so
 
267
//       we can delete the associated moodbar
 
268
//
 
269
//   (private slot) slotFileMoved(): Called when a music file is moved, so
 
270
//       we can move the associated moodbar
265
271
 
266
272
// TODO: off-color single bars in dark areas -- do some interpolation when
267
273
//       averaging.  Big jumps in hues when near black.
275
281
#include "amarok.h"
276
282
#include "amarokconfig.h"
277
283
#include "app.h"
 
284
#include "collectiondb.h"
278
285
#include "debug.h"
279
286
#include "metabundle.h"
 
287
#include "mountpointmanager.h"
280
288
#include "statusbar.h"
281
289
 
282
290
#include <qfile.h>
311
319
    : m_moodbarBroken( false )
312
320
    , m_currentProcess( 0 )
313
321
{
314
 
    connect( App::instance(), SIGNAL( moodbarPrefs( bool, bool, int, bool ) ), 
 
322
    connect( App::instance(), SIGNAL( moodbarPrefs( bool, bool, int, bool ) ),
315
323
             SLOT( slotMoodbarPrefs( bool, bool, int, bool ) ) );
 
324
    connect( CollectionDB::instance(),
 
325
             SIGNAL( fileMoved( const QString &, const QString & ) ),
 
326
             SLOT( slotFileMoved( const QString &, const QString & ) ) );
 
327
    connect( CollectionDB::instance(),
 
328
             SIGNAL( fileMoved( const QString &, const QString &, const QString & ) ),
 
329
             SLOT( slotFileMoved( const QString &, const QString & ) ) );
 
330
    connect( CollectionDB::instance(),
 
331
             SIGNAL( fileDeleted( const QString & ) ),
 
332
             SLOT( slotFileDeleted( const QString & ) ) );
 
333
    connect( CollectionDB::instance(),
 
334
             SIGNAL( fileDeleted( const QString &, const QString & ) ),
 
335
             SLOT( slotFileDeleted( const QString & ) ) );
316
336
}
317
337
 
318
338
 
327
347
      return false;
328
348
 
329
349
    m_mutex.lock();
330
 
    
 
350
 
331
351
    // Check if the currently running job is for that URL
332
 
    if( m_currentProcess != 0  &&  
 
352
    if( m_currentProcess != 0  &&
333
353
        m_currentData.m_url == bundle->url() )
334
354
      {
335
355
        debug() << "MoodServer::queueJob: Not re-queueing already-running job "
353
373
          }
354
374
      }
355
375
 
356
 
    m_jobQueue.append( ProcData( bundle->url(), 
357
 
                                 bundle->url().path(), 
358
 
                                 bundle->moodbar().moodFilename() ) );
 
376
    m_jobQueue.append( ProcData( bundle->url(),
 
377
                                 bundle->url().path(),
 
378
                                 bundle->moodbar().moodFilename( bundle->url() ) ) );
359
379
 
360
 
    debug() << "MoodServer::queueJob: Queued job for " << bundle->url().path() 
 
380
    debug() << "MoodServer::queueJob: Queued job for " << bundle->url().path()
361
381
            << ", " << m_jobQueue.size() << " jobs in queue." << endl;
362
382
 
363
383
    m_mutex.unlock();
375
395
MoodServer::deQueueJob( KURL url )
376
396
{
377
397
    m_mutex.lock();
378
 
    
 
398
 
379
399
    // Can't de-queue running jobs
380
 
    if( m_currentProcess != 0  &&  
 
400
    if( m_currentProcess != 0  &&
381
401
        m_currentData.m_url == url )
382
402
      {
383
403
        debug() << "MoodServer::deQueueJob: Not de-queueing already-running job "
397
417
            if( (*it).m_refcount == 0 )
398
418
              {
399
419
                debug() << "MoodServer::deQueueJob: nobody cares about "
400
 
                        << (*it).m_url.path() 
 
420
                        << (*it).m_url.path()
401
421
                        << " anymore, deleting from queue" << endl;
402
422
                m_jobQueue.erase( it );
403
423
              }
404
 
            
 
424
 
405
425
            else
406
426
              debug() << "MoodServer::deQueueJob: decrementing refcount of "
407
427
                      << (*it).m_url.path() << " to " << (*it).m_refcount
429
449
    return;
430
450
 
431
451
  m_mutex.lock();
432
 
  
 
452
 
433
453
  // Are we already running a process?
434
454
  if( m_jobQueue.isEmpty()  ||  m_currentProcess != 0 )
435
455
    {
450
470
  // Write to outfile.mood.tmp so that new Moodbar instances
451
471
  // don't think the mood data exists while the analyzer is
452
472
  // running.  Then rename the file later.
453
 
  m_currentProcess = new amaroK::Process( this );
 
473
  m_currentProcess = new Amarok::Process( this );
454
474
  m_currentProcess->setPriority( 19 );  // Nice the process
455
 
  *m_currentProcess << KStandardDirs::findExe( "moodbar" ) << "-o" 
 
475
  *m_currentProcess << KStandardDirs::findExe( "moodbar" ) << "-o"
456
476
                    << (m_currentData.m_outfile + ".tmp")
457
477
                    << m_currentData.m_infile;
458
478
 
489
509
MoodServer::slotJobCompleted( KProcess *proc )
490
510
{
491
511
    m_mutex.lock();
492
 
    
 
512
 
493
513
    // Pedantry
494
514
    if( proc != m_currentProcess )
495
515
      warning() << "MoodServer::slotJobCompleted: proc != m_currentProcess!" << endl;
544
564
        m_mutex.unlock();
545
565
        slotNewJob();
546
566
        break;
547
 
        
 
567
 
548
568
      case NoFile:
549
569
        debug() << "MoodServer::slotJobCompleted: moodbar had a problem with "
550
570
                << m_currentData.m_infile << endl;
559
579
        m_mutex.unlock();
560
580
        setMoodbarBroken();
561
581
        break;
562
 
        
 
582
 
563
583
      }
564
584
 
565
 
    emit jobEvent( url, success ? Moodbar::JobStateSucceeded 
 
585
    emit jobEvent( url, success ? Moodbar::JobStateSucceeded
566
586
                                : Moodbar::JobStateFailed );
567
587
}
568
588
 
579
599
    (void) moodier;  (void) alter;  (void) withMusic;
580
600
 
581
601
    // If we have a current process, kill it.  Cleanup happens in
582
 
    // slotJobCompleted() above.  We do *not* want to lock the 
 
602
    // slotJobCompleted() above.  We do *not* want to lock the
583
603
    // mutex when calling this!
584
604
    if( m_currentProcess != 0 )
585
605
      m_currentProcess->kill();
588
608
}
589
609
 
590
610
 
 
611
// When a file is deleted, either manually using Organize Collection or
 
612
// automatically detected using AFT, delete the corresponding mood file.
 
613
void
 
614
MoodServer::slotFileDeleted( const QString &path )
 
615
{
 
616
    QString mood = Moodbar::moodFilename( KURL::fromPathOrURL( path ) );
 
617
    if( mood.isEmpty()  ||  !QFile::exists( mood ) )
 
618
      return;
 
619
 
 
620
    debug() << "MoodServer::slotFileDeleted: deleting " << mood << endl;
 
621
    QFile::remove( mood );
 
622
}
 
623
 
 
624
 
 
625
// When a file is moved, either manually using Organize Collection or
 
626
// automatically using AFT, move the corresponding mood file.
 
627
void
 
628
MoodServer::slotFileMoved( const QString &srcPath, const QString &dstPath )
 
629
{
 
630
    QString srcMood = Moodbar::moodFilename( KURL::fromPathOrURL( srcPath ) );
 
631
    QString dstMood = Moodbar::moodFilename( KURL::fromPathOrURL( dstPath ) );
 
632
 
 
633
    if( srcMood.isEmpty()   ||  dstMood.isEmpty()  ||
 
634
        srcMood == dstMood  ||  !QFile::exists( srcMood ) )
 
635
      return;
 
636
 
 
637
    debug() << "MoodServer::slotFileMoved: moving " << srcMood << " to "
 
638
            << dstMood << endl;
 
639
 
 
640
    Moodbar::copyFile( srcMood, dstMood );
 
641
    QFile::remove( srcMood );
 
642
}
 
643
 
 
644
 
591
645
// This is called when we decide that the moodbar analyzer is
592
646
// never going to work.  Disable further jobs, and let the user
593
647
// know about it.  This should only be called when m_currentProcess == 0.
594
 
void 
 
648
void
595
649
MoodServer::setMoodbarBroken( void )
596
650
{
597
 
    warning() << "Uh oh, it looks like the moodbar analyzer is not going to work" 
 
651
    warning() << "Uh oh, it looks like the moodbar analyzer is not going to work"
598
652
              << endl;
599
653
 
600
 
    amaroK::StatusBar::instance()->longMessage( i18n( 
 
654
    Amarok::StatusBar::instance()->longMessage( i18n(
601
655
        "The Amarok moodbar analyzer program seems to be broken. "
602
656
        "This is probably because the moodbar package is not installed "
603
657
        "correctly.  The moodbar package, installation instructions, and "
604
 
        "troubleshooting help can be found on the wiki page at <a href='" 
 
658
        "troubleshooting help can be found on the wiki page at <a href='"
605
659
        WEBPAGE "'>" WEBPAGE "</a>. "
606
660
        "When the problem is fixed, please restart Amarok."),
607
661
        KDE::StatusBar::Error );
613
667
 
614
668
 
615
669
// Clear the job list and emit signals
616
 
void 
 
670
void
617
671
MoodServer::clearJobs( void )
618
672
{
619
673
    // We don't want to emit jobEvent (or really do anything
620
674
    // external) while the mutex is locked.
621
675
    m_mutex.lock();
622
 
    QValueList<ProcData> queueCopy 
 
676
    QValueList<ProcData> queueCopy
623
677
      = QDeepCopy< QValueList<ProcData> > ( m_jobQueue );
624
678
    m_jobQueue.clear();
625
679
    m_mutex.unlock();
644
698
 
645
699
 
646
700
// The passed MetaBundle _must_ be non-NULL, and the pointer must be valid
647
 
// as long as this instance is alive.  The Moodbar is only meant to be a 
 
701
// as long as this instance is alive.  The Moodbar is only meant to be a
648
702
// member of a MetaBundle, in other words.
649
703
 
650
704
Moodbar::Moodbar( MetaBundle *mb )
656
710
}
657
711
 
658
712
 
659
 
// If we have any pending jobs, de-queue them.  The use case I 
 
713
// If we have any pending jobs, de-queue them.  The use case I
660
714
// have in mind is if the user has the moodbar column displayed
661
715
// and adds all his/her tracks to the playlist, then deletes
662
716
// them again.
668
722
 
669
723
 
670
724
// MetaBundle's are often assigned using operator=, so so are we.
671
 
Moodbar& 
 
725
Moodbar&
672
726
Moodbar::operator=( const Moodbar &mood )
673
727
{
674
728
    // Need to check this before locking both!
681
735
    State oldState = m_state;
682
736
    KURL oldURL    = m_url;
683
737
 
684
 
    m_data    = mood.m_data;  
 
738
    m_data    = mood.m_data;
685
739
    m_pixmap  = mood.m_pixmap;
686
740
    m_state   = mood.m_state;
687
741
    m_url     = mood.m_url;
691
745
    // so those should be updated too.
692
746
    if( JOB_PENDING( m_state )  &&  !JOB_PENDING( oldState ) )
693
747
      {
694
 
        connect( MoodServer::instance(), 
 
748
        connect( MoodServer::instance(),
695
749
                 SIGNAL( jobEvent( KURL, int ) ),
696
750
                 SLOT( slotJobEvent( KURL, int ) ) );
697
751
        // Increase the refcount for this job.  Use mood.m_bundle
705
759
        MoodServer::instance()->disconnect( this, SLOT( slotJobEvent( KURL, int ) ) );
706
760
        MoodServer::instance()->deQueueJob( oldURL );
707
761
      }
708
 
      
 
762
 
709
763
    mood.m_mutex.unlock();
710
764
    m_mutex.unlock();
711
765
 
751
805
  // Apparently this is the wrong hack -- don't detach urls
752
806
  //QString url( QDeepCopy<QString>( m_url.url() ) );
753
807
  //m_url = KURL::fromPathOrURL( url );
754
 
  
 
808
 
755
809
  m_mutex.unlock();
756
810
}
757
811
 
760
814
// returns true, this instance must be able to draw().  This may
761
815
// change the state to CantLoad, but usually leaves the state
762
816
// untouched.
763
 
bool 
 
817
bool
764
818
Moodbar::dataExists( void )
765
819
{
766
820
    // Put this first for efficiency
768
822
      return true;
769
823
 
770
824
    // Should we bother checking for the file?
771
 
    if( m_state == CantLoad    ||  
772
 
        JOB_PENDING( m_state ) || 
773
 
        m_state == JobFailed   ||  
 
825
    if( m_state == CantLoad    ||
 
826
        JOB_PENDING( m_state ) ||
 
827
        m_state == JobFailed   ||
774
828
        !canHaveMood() )
775
829
      return false;
776
830
 
796
850
    // Don't try to analyze it if we can't even determine it has a length
797
851
    // If for some reason we can't determine a file name, give up
798
852
    // If the moodbar is disabled, set to CantLoad -- if the user re-enables
799
 
    // the moodbar, we'll be reset() anyway.  
 
853
    // the moodbar, we'll be reset() anyway.
800
854
    if( !AmarokConfig::showMoodbar()   ||
801
855
        !m_bundle->url().isLocalFile() ||
802
856
        !m_bundle->length()            ||
803
 
        moodFilename().isEmpty() )  
 
857
        moodFilename( m_bundle->url() ).isEmpty() )
804
858
      {
805
859
        m_state = CantLoad;
806
860
        return false;
813
867
// Ask MoodServer to queue an analyzer job for us if necessary.  This
814
868
// method will only do something the first time it's called, as it's
815
869
// guaranteed to change the state from Unloaded.
816
 
void 
 
870
void
817
871
Moodbar::load( void )
818
872
{
819
873
    if( m_state != Unloaded )
843
897
      }
844
898
 
845
899
    // Ok no more excuses, we have to queue a job
846
 
    connect( MoodServer::instance(), 
 
900
    connect( MoodServer::instance(),
847
901
             SIGNAL( jobEvent( KURL, int ) ),
848
902
             SLOT( slotJobEvent( KURL, int ) ) );
849
903
    bool isRunning = MoodServer::instance()->queueJob( m_bundle );
894
948
 
895
949
    // If we get here it means the analyzer job went wrong, but
896
950
    // somehow the MoodServer didn't know about it
897
 
    debug() << "WARNING: Failed to open file " << moodFilename() 
 
951
    debug() << "WARNING: Failed to open file " << moodFilename( m_bundle->url() )
898
952
            << " -- something is very wrong" << endl;
899
953
    m_state = JobFailed;
900
954
    m_mutex.unlock();
958
1012
 
959
1013
        uint n = end - start;
960
1014
        bar =  QColor( int( r / float( n ) ),
961
 
                       int( g / float( n ) ), 
 
1015
                       int( g / float( n ) ),
962
1016
                       int( b / float( n ) ), QColor::Rgb );
963
1017
 
964
1018
        /* Snap to the HSV values for later */
967
1021
 
968
1022
        screenColors.push_back( bar );
969
1023
      }
970
 
    
 
1024
 
971
1025
    // Paint the bars.  This is Gav's painting code -- it breaks up the
972
1026
    // monotony of solid-color vertical bars by playing with the saturation
973
 
    // and value.  
 
1027
    // and value.
974
1028
 
975
1029
    for( int x = 0; x < width; x++ )
976
1030
      {
982
1036
            float coeff2 = 1.f - ((1.f - coeff) * (1.f - coeff));
983
1037
            coeff = 1.f - (1.f - coeff) / 2.f;
984
1038
            coeff2 = 1.f - (1.f - coeff2) / 2.f;
985
 
            paint.setPen( QColor( h, 
986
 
                CLAMP( 0, int( float( s ) * coeff ), 255 ), 
987
 
                CLAMP( 0, int( 255.f - (255.f - float( v )) * coeff2), 255 ), 
 
1039
            paint.setPen( QColor( h,
 
1040
                CLAMP( 0, int( float( s ) * coeff ), 255 ),
 
1041
                CLAMP( 0, int( 255.f - (255.f - float( v )) * coeff2), 255 ),
988
1042
                QColor::Hsv ) );
989
1043
            paint.drawPoint(x, y);
990
1044
            paint.drawPoint(x, height - 1 - y);
1018
1072
    if( m_state == Loaded )
1019
1073
      return true;
1020
1074
 
1021
 
    QString path = moodFilename();
 
1075
    QString path = moodFilename( m_bundle->url() );
1022
1076
    if( path.isEmpty() )
1023
1077
      return false;
1024
1078
 
1026
1080
 
1027
1081
    QFile moodFile( path );
1028
1082
 
1029
 
    if( !QFile::exists( path )  ||  
 
1083
    if( !QFile::exists( path )  ||
1030
1084
        !moodFile.open( IO_ReadOnly ) )
1031
1085
      {
1032
1086
        // If the user has changed his/her preference about where to
1033
1087
        // store the mood files, he/she might have the .mood file
1034
1088
        // in the other place, so we should check there before giving
1035
1089
        // up.
1036
 
        
1037
 
        QString path2 = moodFilename( !AmarokConfig::moodsWithMusic() );
 
1090
 
 
1091
        QString path2 = moodFilename( m_bundle->url(),
 
1092
                                      !AmarokConfig::moodsWithMusic() );
1038
1093
        moodFile.setName( path2 );
1039
 
        
 
1094
 
1040
1095
        if( !QFile::exists( path2 )  ||
1041
1096
            !moodFile.open( IO_ReadOnly ) )
1042
1097
          return false;
1043
1098
 
1044
 
        debug() << "Moodbar::readFile: Found a file at " << path2 
 
1099
        debug() << "Moodbar::readFile: Found a file at " << path2
1045
1100
                << " instead, using that and copying." << endl;
1046
1101
 
1047
 
        QByteArray contents = moodFile.readAll();
1048
1102
        moodFile.close();
 
1103
        if( !copyFile( path2, path ) )
 
1104
          return false;
1049
1105
        moodFile.setName( path );
1050
 
        if( !moodFile.open( IO_WriteOnly ) )
1051
 
          return false;
1052
 
        bool res = ( uint( moodFile.writeBlock( contents ) ) == contents.size() );
1053
 
        moodFile.close();
1054
 
        if( !res )
1055
 
          return false;
1056
1106
        if( !moodFile.open( IO_ReadOnly ) )
1057
1107
          return false;
1058
1108
      }
1059
1109
 
1060
1110
    int r, g, b, samples = moodFile.size() / 3;
1061
 
    debug() << "Moodbar::readFile: File " << path 
 
1111
    debug() << "Moodbar::readFile: File " << path
1062
1112
            << " opened. Proceeding to read contents... s=" << samples << endl;
1063
1113
 
1064
1114
    // This would be bad.
1065
1115
    if( samples == 0 )
1066
1116
      {
1067
 
        debug() << "Moodbar::readFile: File " << moodFile.name() 
 
1117
        debug() << "Moodbar::readFile: File " << moodFile.name()
1068
1118
                << " is corrupted, removing." << endl;
1069
1119
        moodFile.remove();
1070
1120
        return false;
1073
1123
    int huedist[360], mx = 0; // For alterMood
1074
1124
    int modalHue[NUM_HUES];   // For m_hueSort
1075
1125
    int h, s, v;
1076
 
    
 
1126
 
1077
1127
    memset( modalHue, 0, sizeof( modalHue ) );
1078
1128
    memset( huedist, 0, sizeof( huedist ) );
1079
1129
 
1084
1134
        g = moodFile.getch();
1085
1135
        b = moodFile.getch();
1086
1136
 
1087
 
        m_data.push_back( QColor( CLAMP( 0, r, 255 ), 
1088
 
                                  CLAMP( 0, g, 255 ), 
 
1137
        m_data.push_back( QColor( CLAMP( 0, r, 255 ),
 
1138
                                  CLAMP( 0, g, 255 ),
1089
1139
                                  CLAMP( 0, b, 255 ), QColor::Rgb ) );
1090
1140
 
1091
1141
        // Make a histogram of hues
1098
1148
 
1099
1149
    // Make moodier -- copied straight from Gav Wood's code
1100
1150
    // Here's an explanation of the algorithm:
1101
 
    // 
 
1151
    //
1102
1152
    // The "input" hue for each bar is mapped to a hue between
1103
1153
    // rangeStart and (rangeStart + rangeDelta).  The mapping is
1104
1154
    // determined by the hue histogram, huedist[], which is calculated
1107
1157
    // hues that are close together, then these hues are separated,
1108
1158
    // and the space between spikes in the hue histogram is
1109
1159
    // compressed.  Here we consider a hue value to be a "spike" in
1110
 
    // the hue histogram if the number of samples in that bin is 
 
1160
    // the hue histogram if the number of samples in that bin is
1111
1161
    // greater than the threshold variable.
1112
 
    // 
 
1162
    //
1113
1163
    // As an example, suppose we have 100 samples, and that
1114
1164
    //    threshold = 10  rangeStart = 0  rangeDelta = 288
1115
1165
    // Suppose that we have 10 samples at each of 99,100,101, and 200.
1149
1199
        switch( AmarokConfig::alterMood() )
1150
1200
          {
1151
1201
          case 1: // Angry
1152
 
            threshold  = samples / 360 * 9; 
1153
 
            rangeStart = 45; 
1154
 
            rangeDelta = -45; 
1155
 
            sat        = 200; 
1156
 
            val        = 100; 
 
1202
            threshold  = samples / 360 * 9;
 
1203
            rangeStart = 45;
 
1204
            rangeDelta = -45;
 
1205
            sat        = 200;
 
1206
            val        = 100;
1157
1207
            break;
1158
1208
 
1159
1209
          case 2: // Frozen
1160
 
            threshold  = samples / 360 * 1; 
1161
 
            rangeStart = 140; 
1162
 
            rangeDelta = 160; 
1163
 
            sat        = 50; 
1164
 
            val        = 100; 
 
1210
            threshold  = samples / 360 * 1;
 
1211
            rangeStart = 140;
 
1212
            rangeDelta = 160;
 
1213
            sat        = 50;
 
1214
            val        = 100;
1165
1215
            break;
1166
1216
 
1167
1217
          default: // Happy
1168
 
            threshold  = samples / 360 * 2; 
1169
 
            rangeStart = 0; 
1170
 
            rangeDelta = 359; 
1171
 
            sat        = 150; 
 
1218
            threshold  = samples / 360 * 2;
 
1219
            rangeStart = 0;
 
1220
            rangeDelta = 359;
 
1221
            sat        = 150;
1172
1222
            val        = 250;
1173
1223
          }
1174
1224
 
1175
 
        debug() << "ReadMood: Appling filter t=" << threshold 
1176
 
                << ", rS=" << rangeStart << ", rD=" << rangeDelta 
 
1225
        debug() << "ReadMood: Appling filter t=" << threshold
 
1226
                << ", rS=" << rangeStart << ", rD=" << rangeDelta
1177
1227
                << ", s=" << sat << "%, v=" << val << "%" << endl;
1178
1228
 
1179
 
        // On average, huedist[i] = samples / 360.  This counts the 
 
1229
        // On average, huedist[i] = samples / 360.  This counts the
1180
1230
        // number of samples over the threshold, which is usually
1181
1231
        // 1, 2, 9, etc. times the average samples in each bin.
1182
1232
        // The total determines how many output hues there are,
1183
1233
        // evenly spaced between rangeStart and rangeStart + rangeDelta.
1184
 
        for( int i = 0; i < 360; i++ ) 
1185
 
          if( huedist[i] > threshold ) 
 
1234
        for( int i = 0; i < 360; i++ )
 
1235
          if( huedist[i] > threshold )
1186
1236
            total++;
1187
1237
 
1188
1238
        if( total < 360 && total > 0 )
1189
1239
          {
1190
1240
            // Remap the hue values to be between rangeStart and
1191
1241
            // rangeStart + rangeDelta.  Every time we see an input hue
1192
 
            // above the threshold, increment the output hue by 
 
1242
            // above the threshold, increment the output hue by
1193
1243
            // (1/total) * rangeDelta.
1194
1244
            for( int i = 0, n = 0; i < 360; i++ )
1195
 
              huedist[i] = ( ( huedist[i] > threshold ? n++ : n ) 
 
1245
              huedist[i] = ( ( huedist[i] > threshold ? n++ : n )
1196
1246
                             * rangeDelta / total + rangeStart ) % 360;
1197
1247
 
1198
 
            // Now huedist is a hue mapper: huedist[h] is the new hue value 
 
1248
            // Now huedist is a hue mapper: huedist[h] is the new hue value
1199
1249
            // for a bar with hue h
1200
1250
 
1201
1251
            for(uint i = 0; i < m_data.size(); i++)
1202
 
              { 
 
1252
              {
1203
1253
                m_data[i].getHsv( &h, &s, &v );
1204
1254
                if( h < 0 ) h = 0;  else h = h % 360;
1205
 
                m_data[i].setHsv( CLAMP( 0, huedist[h], 359 ), 
1206
 
                                  CLAMP( 0, s * sat / 100, 255 ), 
 
1255
                m_data[i].setHsv( CLAMP( 0, huedist[h], 359 ),
 
1256
                                  CLAMP( 0, s * sat / 100, 255 ),
1207
1257
                                  CLAMP( 0, v * val / 100, 255 ) );
1208
1258
 
1209
 
                modalHue[CLAMP(0, huedist[h] * NUM_HUES / 360, NUM_HUES - 1)] 
 
1259
                modalHue[CLAMP(0, huedist[h] * NUM_HUES / 360, NUM_HUES - 1)]
1210
1260
                  += (v * val / 100);
1211
1261
              }
1212
1262
          }
1214
1264
 
1215
1265
    // Calculate m_hueSort.  This is a 3-digit number in base NUM_HUES,
1216
1266
    // where the most significant digit is the first strongest hue, the
1217
 
    // second digit is the second strongest hue, and the third digit 
 
1267
    // second digit is the second strongest hue, and the third digit
1218
1268
    // is the third strongest.  This code was written by Gav Wood.
1219
 
  
 
1269
 
1220
1270
    m_hueSort = 0;
1221
1271
    mx = 0;
1222
 
    for( int i = 1; i < NUM_HUES; i++ ) 
1223
 
      if( modalHue[i] > modalHue[mx] ) 
 
1272
    for( int i = 1; i < NUM_HUES; i++ )
 
1273
      if( modalHue[i] > modalHue[mx] )
1224
1274
        mx = i;
1225
1275
    m_hueSort = mx * NUM_HUES * NUM_HUES;
1226
1276
    modalHue[mx] = 0;
1227
1277
 
1228
1278
    mx = 0;
1229
 
    for( int i = 1; i < NUM_HUES; i++ ) 
1230
 
      if( modalHue[i] > modalHue[mx] ) 
 
1279
    for( int i = 1; i < NUM_HUES; i++ )
 
1280
      if( modalHue[i] > modalHue[mx] )
1231
1281
        mx = i;
1232
1282
    m_hueSort += mx * NUM_HUES;
1233
1283
    modalHue[mx] = 0;
1234
1284
 
1235
1285
    mx = 0;
1236
 
    for( int i = 1; i < NUM_HUES; i++ ) 
1237
 
      if( modalHue[i] > modalHue[mx] ) 
 
1286
    for( int i = 1; i < NUM_HUES; i++ )
 
1287
      if( modalHue[i] > modalHue[mx] )
1238
1288
        mx = i;
1239
1289
    m_hueSort += mx;
1240
 
    
 
1290
 
1241
1291
 
1242
1292
    debug() << "Moodbar::readFile: All done." << endl;
1243
1293
 
1253
1303
// return QString::null.
1254
1304
 
1255
1305
QString
1256
 
Moodbar::moodFilename( void )
 
1306
Moodbar::moodFilename( const KURL &url )
1257
1307
{
1258
 
  return moodFilename( AmarokConfig::moodsWithMusic() );
 
1308
  return moodFilename( url, AmarokConfig::moodsWithMusic() );
1259
1309
}
1260
1310
 
1261
 
QString 
1262
 
Moodbar::moodFilename( bool withMusic )
 
1311
QString
 
1312
Moodbar::moodFilename( const KURL &url, bool withMusic )
1263
1313
{
1264
1314
    // No need to lock the object
1265
1315
 
1266
 
    QString path = m_bundle->url().path();
1267
 
    path.truncate(path.findRev('.'));
1268
 
 
1269
 
    if (path.isEmpty())  // Weird...
1270
 
      return QString::null;
 
1316
    QString path;
1271
1317
 
1272
1318
    if( withMusic )
1273
1319
      {
 
1320
        path = url.path();
 
1321
        path.truncate(path.findRev('.'));
 
1322
 
 
1323
        if (path.isEmpty())  // Weird...
 
1324
          return QString::null;
 
1325
 
1274
1326
        path += ".mood";
1275
1327
        int slash = path.findRev('/') + 1;
1276
1328
        QString dir  = path.left(slash);
1277
1329
        QString file = path.right(path.length() - slash);
1278
 
        path = dir + "." + file;
 
1330
        path = dir + '.' + file;
1279
1331
      }
 
1332
 
1280
1333
    else
1281
1334
      {
1282
 
        path.replace('/', ',');
 
1335
        // The moodbar file is {device id},{relative path}.mood}
 
1336
        int deviceid = MountPointManager::instance()->getIdForUrl( url );
 
1337
        KURL relativePath;
 
1338
        MountPointManager::instance()->getRelativePath( deviceid,
 
1339
            url, relativePath );
 
1340
        path = relativePath.path();
 
1341
        path.truncate(path.findRev('.'));
 
1342
 
 
1343
        if (path.isEmpty())  // Weird...
 
1344
          return QString::null;
 
1345
 
 
1346
        path = QString::number( deviceid ) + ','
 
1347
          + path.replace('/', ',') + ".mood";
 
1348
 
1283
1349
        // Creates the path if necessary
1284
 
        path = ::locateLocal("data", "amarok/moods/" + path + ".mood");
 
1350
        path = ::locateLocal( "data", "amarok/moods/" + path );
1285
1351
      }
1286
1352
 
1287
1353
    return path;
1288
1354
}
1289
1355
 
1290
1356
 
 
1357
// Quick-n-dirty -->synchronous<-- file copy (the GUI needs its
 
1358
// moodbars immediately!)
 
1359
bool
 
1360
Moodbar::copyFile( const QString &srcPath, const QString &dstPath )
 
1361
{
 
1362
  QFile file( srcPath );
 
1363
  if( !file.open( IO_ReadOnly ) )
 
1364
    return false;
 
1365
  QByteArray contents = file.readAll();
 
1366
  file.close();
 
1367
  file.setName( dstPath );
 
1368
  if( !file.open( IO_WriteOnly | IO_Truncate ) )
 
1369
    return false;
 
1370
  bool res = ( uint( file.writeBlock( contents ) ) == contents.size() );
 
1371
  file.close();
 
1372
  return res;
 
1373
}
 
1374
 
 
1375
 
 
1376
 
1291
1377
// Can we find the moodbar program?
1292
 
bool 
 
1378
bool
1293
1379
Moodbar::executableExists( void )
1294
1380
{
1295
1381
  return !(KStandardDirs::findExe( "moodbar" ).isNull());