1 /***************************************************************************
4 begin : Mit Okt 23 14:35:18 CEST 2002
5 copyright : (C) 2002 by Mark Kretschmann
7 ***************************************************************************/
9 /***************************************************************************
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
16 ***************************************************************************/
20 #include "amarokconfig.h"
21 #include "amarokdbushandler.h"
22 #include "atomicstring.h"
23 #include "collectiondb.h"
24 #include "CollectionManager.h"
25 #include "ConfigDialog.h"
26 #include "context/ContextView.h"
27 #include "CoverFetcher.h"
29 #include "enginebase.h"
30 #include "enginecontroller.h"
31 #include "equalizersetup.h"
32 #include "MainWindow.h"
33 #include "mediabrowser.h"
35 #include "metabundle.h"
36 #include "mountpointmanager.h"
38 #include "playlist/PlaylistModel.h"
40 #include "pluginmanager.h"
41 #include "refreshimages.h"
42 #include "scriptmanager.h"
43 #include "scrobbler.h"
44 #include "ContextStatusBar.h"
46 #include "threadmanager.h"
47 #include "tracktooltip.h" //engineNewMetaData()
48 #include "TheInstances.h"
49 #include "metadata/tplugins.h"
55 #include <KCmdLineArgs> //initCliArgs()
56 #include <KConfigDialogManager>
57 #include <KCursor> //Amarok::OverrideCursor
58 #include <KEditToolBar> //slotConfigToolbars()
59 #include <KGlobalAccel> //initGlobalShortcuts()
60 #include <KGlobalSettings> //applyColorScheme()
61 #include <KIO/CopyJob>
62 #include <KIconLoader> //amarok Icon
64 #include <KJobUiDelegate>
66 #include <KRun> //Amarok::invokeBrowser()
68 #include <KShortcutsDialog> //slotConfigShortcuts()
69 #include <KSplashScreen>
70 #include <KStandardDirs>
72 #include <QDBusInterface>
74 #include <QEventLoop> //applySettings()
76 #include <QPixmapCache>
77 #include <QTimer> //showHyperThreadingWarning()
78 #include <QToolTip> //default tooltip for trayicon
79 #include <QtDBus/QtDBus>
82 QMutex
Amarok::globalDirsMutex
;
84 int App::mainThreadId
= 0;
87 #include <CoreFoundation/CoreFoundation.h>
88 extern void setupEventHandler_mac(long);
91 AMAROK_EXPORT KAboutData
aboutData( "amarok", 0,
92 ki18n( "Amarok" ), APP_VERSION
,
93 ki18n( "The audio player for KDE" ), KAboutData::License_GPL
,
94 ki18n( "(C) 2002-2003, Mark Kretschmann\n(C) 2003-2007, The Amarok Development Squad" ),
95 ki18n( "IRC:\nirc.freenode.net - #amarok, #amarok.de, #amarok.es\n\nFeedback:\namarok@kde.org\n\n(Build Date: " __DATE__
")" ),
96 ( "http://amarok.kde.org" ) );
100 : KUniqueApplication()
105 if( AmarokConfig::showSplashscreen() )
107 QPixmap
splashpix( KStandardDirs().findResource("data", "amarok/images/splash_screen.jpg") );
108 m_splash
= new KSplashScreen( splashpix
, Qt::WindowStaysOnTopHint
);
112 registerTaglibPlugins();
114 qRegisterMetaType
<MetaBundle
>();
116 qRegisterMetaType
<Meta::DataPtr
>();
117 qRegisterMetaType
<Meta::DataList
>();
118 qRegisterMetaType
<Meta::TrackPtr
>();
119 qRegisterMetaType
<Meta::TrackList
>();
120 qRegisterMetaType
<Meta::AlbumPtr
>();
121 qRegisterMetaType
<Meta::AlbumList
>();
122 qRegisterMetaType
<Meta::ArtistPtr
>();
123 qRegisterMetaType
<Meta::ArtistList
>();
124 qRegisterMetaType
<Meta::GenrePtr
>();
125 qRegisterMetaType
<Meta::GenreList
>();
126 qRegisterMetaType
<Meta::ComposerPtr
>();
127 qRegisterMetaType
<Meta::ComposerList
>();
128 qRegisterMetaType
<Meta::YearPtr
>();
129 qRegisterMetaType
<Meta::YearList
>();
132 //make sure we have enough cache space for all our crazy svg stuff
133 QPixmapCache::setCacheLimit ( 20 * 1024 );
136 // this is inspired by OpenSceneGraph: osgDB/FilePath.cpp
138 // Start with the the Bundle PlugIns directory.
140 // Get the main bundle first. No need to retain or release it since
141 // we are not keeping a reference
142 CFBundleRef myBundle
= CFBundleGetMainBundle();
145 // CFBundleGetMainBundle will return a bundle ref even if
146 // the application isn't part of a bundle, so we need to
148 // if the path to the bundle ends in ".app" to see if it is
150 // proper application bundle. If it is, the plugins path is
152 CFURLRef urlRef
= CFBundleCopyBundleURL(myBundle
);
155 char bundlePath
[1024];
156 if( CFURLGetFileSystemRepresentation( urlRef
, true, (UInt8
*)bundlePath
, sizeof(bundlePath
) ) )
158 QByteArray
bp( bundlePath
);
159 size_t len
= bp
.length();
160 if( len
> 4 && bp
.right( 4 ) == ".app" )
162 bp
.append( "/Contents/MacOS" );
163 QByteArray path
= getenv( "PATH" );
164 if( path
.length() > 0 )
169 debug() << "setting PATH=" << path
;
170 setenv("PATH", path
, 1);
173 // docs say we are responsible for releasing CFURLRef
179 //needs to be created before the wizard
180 new Amarok::DbusPlayerHandler(); // Must be created first
181 new Amarok::DbusPlaylistHandler();
182 new Amarok::DbusPlaylistBrowserHandler();
183 new Amarok::DbusContextHandler();
184 new Amarok::DbusCollectionHandler();
185 // new Amarok::DbusMediaBrowserHandler();
186 new Amarok::DbusScriptHandler();
188 // tell AtomicString that this is the GUI thread
189 if ( !AtomicString::isMainThread() )
190 qWarning("AtomicString was initialized from a thread other than the GUI "
191 "thread. This could lead to memory leaks.");
194 setupEventHandler_mac((long)this);
196 QDBusConnection::sessionBus().registerService("org.kde.amarok");
197 QTimer::singleShot( 0, this, SLOT( continueInit() ) );
207 // Hiding the OSD before exit prevents crash
208 Amarok::OSD::instance()->hide();
210 EngineBase
* const engine
= EngineController::engine();
212 if ( AmarokConfig::resumePlayback() ) {
213 if ( engine
->state() != Engine::Empty
) {
214 AmarokConfig::setResumeTrack( EngineController::instance()->playingURL().prettyUrl() );
215 AmarokConfig::setResumeTime( engine
->position() );
217 else AmarokConfig::setResumeTrack( QString() ); //otherwise it'll play previous resume next time!
220 EngineController::instance()->endSession(); //records final statistics
221 EngineController::instance()->detach( this );
223 // do even if trayicon is not shown, it is safe
224 Amarok::config().writeEntry( "HiddenOnExit", mainWindow()->isHidden() );
226 CollectionDB::instance()->stopScan();
228 ThreadManager::deleteInstance(); //waits for jobs to finish
230 // this must be deleted before the connection to the Xserver is
231 // severed, or we risk a crash when the QApplication is exited,
232 // I asked Trolltech! *smug*
233 delete Amarok::OSD::instance();
235 AmarokConfig::setVersion( APP_VERSION
);
236 AmarokConfig::self()->writeConfig();
238 mainWindow()->deleteBrowsers();
242 // work around for KUniqueApplication being not completely implemented on windows
243 QDBusConnectionInterface
* dbusService
;
244 if (QDBusConnection::sessionBus().isConnected() && (dbusService
= QDBusConnection::sessionBus().interface()))
245 dbusService
->unregisterService("org.kde.amarok");
250 #include <QStringList>
254 // grabbed from KsCD source, kompatctdisk.cpp
255 QString
urlToDevice(const QString
& device
)
257 KUrl
deviceUrl(device
);
258 if (deviceUrl
.protocol() == "media" || deviceUrl
.protocol() == "system")
260 debug() << "WARNING: urlToDevice needs to be reimplemented with KDE4 technology, it's just a stub at the moment";
261 QDBusInterface
mediamanager( "org.kde.kded", "/modules/mediamanager", "org.kde.MediaManager" );
262 QDBusReply
<QStringList
> reply
= mediamanager
.call( "properties",deviceUrl
.fileName() );
263 if (!reply
.isValid()) {
264 debug() << "Invalid reply from mediamanager";
267 QStringList properties
= reply
;
268 if( properties
.count()< 6 )
270 debug() << "Reply from mediamanager " << properties
[5];
271 return properties
[5];
280 void App::handleCliArgs() //static
282 KCmdLineArgs
* const args
= KCmdLineArgs::parsedArgs();
284 if ( args
->isSet( "cwd" ) )
286 KCmdLineArgs::setCwd( args
->getOption( "cwd" ).toLocal8Bit() );
289 bool haveArgs
= false;
290 if ( args
->count() > 0 )
295 for( int i
= 0; i
< args
->count(); i
++ )
297 KUrl url
= args
->url( i
);
299 // if( url.protocol() == "itpc" || url.protocol() == "pcast" )
300 // PlaylistBrowserNS::instance()->addPodcast( url );
305 int options
= Playlist::AppendAndPlay
;
306 if( args
->isSet( "queue" ) )
307 options
= Playlist::Queue
;
308 else if( args
->isSet( "append" ) || args
->isSet( "enqueue" ) )
309 options
= Playlist::Append
;
310 else if( args
->isSet( "load" ) )
311 options
= Playlist::Replace
;
313 if( args
->isSet( "play" ) )
314 options
|= Playlist::DirectPlay
;
316 Meta::TrackList tracks
= CollectionManager::instance()->tracksForUrls( list
);
317 The::playlistModel()->insertOptioned( tracks
, options
);
320 //we shouldn't let the user specify two of these since it is pointless!
321 //so we prioritise, pause > stop > play > next > prev
322 //thus pause is the least destructive, followed by stop as brakes are the most important bit of a car(!)
323 //then the others seemed sensible. Feel free to modify this order, but please leave justification in the cvs log
324 //I considered doing some sanity checks (eg only stop if paused or playing), but decided it wasn't worth it
325 else if ( args
->isSet( "pause" ) )
328 EngineController::instance()->pause();
330 else if ( args
->isSet( "stop" ) )
333 EngineController::instance()->stop();
335 else if ( args
->isSet( "play-pause" ) )
338 EngineController::instance()->playPause();
340 else if ( args
->isSet( "play" ) ) //will restart if we are playing
343 EngineController::instance()->play();
345 else if ( args
->isSet( "next" ) )
348 EngineController::instance()->next();
350 else if ( args
->isSet( "previous" ) )
353 EngineController::instance()->previous();
355 else if (args
->isSet("cdplay"))
358 QString device
= args
->getOption("cdplay");
360 if (EngineController::engine()->getAudioCDContents(device
, urls
)) {
361 Meta::TrackList tracks
= CollectionManager::instance()->tracksForUrls( urls
);
362 The::playlistModel()->insertOptioned(
363 tracks
, Playlist::Replace
|Playlist::DirectPlay
);
364 } else { // Default behaviour
366 "Sorry, the engine doesn't support direct play from AudioCD..."
371 if ( args
->isSet( "toggle-playlist-window" ) )
374 pApp
->mainWindow()->showHide();
377 //FIXME Debug output always enabled for now. MUST BE REVERTED BEFORE RELEASE.
378 Amarok::config().writeEntry( "Debug Enabled", true );
379 //Amarok::config().writeEntry( "Debug Enabled", args->isSet( "debug" ) );
381 static bool firstTime
= true;
382 if( !firstTime
&& !haveArgs
)
383 pApp
->mainWindow()->activate();
386 args
->clear(); //free up memory
390 /////////////////////////////////////////////////////////////////////////////////////
392 /////////////////////////////////////////////////////////////////////////////////////
394 void App::initCliArgs( int argc
, char *argv
[] ) //static
396 KCmdLineArgs::reset();
397 KCmdLineArgs::init( argc
, argv
, &::aboutData
); //calls KCmdLineArgs::addStdCmdLineOptions()
399 KCmdLineOptions options
;
400 options
.add("+[URL(s)]", ki18n( "Files/URLs to open" ));
402 options
.add("previous", ki18n( "Skip backwards in playlist" ));
404 options
.add("play", ki18n( "Start playing current playlist" ));
406 options
.add("play-pause", ki18n( "Play if stopped, pause if playing" ));
407 options
.add("pause", ki18n( "Pause playback" ));
409 options
.add("stop", ki18n( "Stop playback" ));
411 options
.add("next", ki18n( "Skip forwards in playlist" ));
412 options
.add(":", ki18n("Additional options:"));
414 options
.add("append", ki18n( "Append files/URLs to playlist" ));
416 options
.add("enqueue", ki18n("See append, available for backwards compatability"));
417 options
.add("queue", ki18n("Queue URLs after the currently playing track"));
419 options
.add("load", ki18n("Load URLs, replacing current playlist"));
421 options
.add("debug", ki18n("Print verbose debugging information"));
423 options
.add("toggle-playlist-window", ki18n("Toggle the Playlist-window"));
424 options
.add("wizard", ki18n( "Run first-run wizard" ));
425 options
.add("engine <name>", ki18n( "Use the <name> engine" ));
426 options
.add("cwd <directory>", ki18n( "Base for relative filenames/URLs" ));
427 options
.add("cdplay <device>", ki18n("Play an AudioCD from <device> or system:/media/<device>"));
428 KCmdLineArgs::addCmdLineOptions( options
); //add our own options
432 void App::initGlobalShortcuts()
434 EngineController
* const ec
= EngineController::instance();
437 action
= new KAction( i18n( "Play" ), mainWindow() );
438 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_X
) );
439 connect( action
, SIGNAL( triggered() ), ec
, SLOT( play() ) );
441 // m_pGlobalAccel->insert( "pause", i18n( "Pause" ), 0, 0, 0, ec, SLOT( pause() ), true, true );
442 action
= new KAction( i18n( "Pause" ), mainWindow() );
443 connect( action
, SIGNAL( triggered() ), ec
, SLOT( pause() ) );
445 action
= new KAction( i18n( "Play/Pause" ), mainWindow() );
446 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_C
) );
447 connect( action
, SIGNAL( triggered() ), ec
, SLOT( playPause() ) );
449 action
= new KAction( i18n( "Stop" ), mainWindow() );
450 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_V
) );
451 connect( action
, SIGNAL( triggered() ), ec
, SLOT( stop() ) );
453 // m_pGlobalAccel->insert( "stop_after_global", i18n( "Stop Playing After Current Track" ), 0, KKey("WIN+CTRL+v"), 0, Playlist::instance()->qscrollview(), SLOT( toggleStopAfterCurrentTrack() ), true, true );
454 // action = new KAction( i18n( "Stop Playing After Current Track" ), mainWindow() );
455 // action->setGlobalShortcut( KShortcut( Qt::META + Qt::CTRL + Qt::Key_V ) );
457 // connect( action, SIGNAL( triggered() ), Playlist::instance()->qscrollview(), SLOT( toggleStopAfterCurrentTrack() ) );
459 action
= new KAction( i18n( "Next Track" ), mainWindow() );
460 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_B
) );
461 connect( action
, SIGNAL( triggered() ), The::playlistModel(), SLOT( next() ) );
463 action
= new KAction( i18n( "Previous Track" ), mainWindow() );
464 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_Z
) );
465 connect( action
, SIGNAL( triggered() ), The::playlistModel(), SLOT( previous() ) );
467 action
= new KAction( i18n( "Increase Volume" ), mainWindow() );
468 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_Plus
) );
469 connect( action
, SIGNAL( triggered() ), ec
, SLOT( increaseVolume() ) );
471 action
= new KAction( i18n( "Decrease Volume" ), mainWindow() );
472 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_Minus
) );
473 connect( action
, SIGNAL( triggered() ), ec
, SLOT( decreaseVolume() ) );
476 action
= new KAction( i18n( "Seek Forward" ), mainWindow() );
477 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::SHIFT
+ Qt::Key_Plus
) );
478 connect( action
, SIGNAL( triggered() ), ec
, SLOT( seekForward() ) );
481 action
= new KAction( i18n( "Seek Backward" ), mainWindow() );
482 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::SHIFT
+ Qt::Key_Minus
) );
483 connect( action
, SIGNAL( triggered() ), ec
, SLOT( seekBackward() ) );
485 action
= new KAction( i18n( "Add Media..." ), mainWindow() );
486 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_A
) );
487 connect( action
, SIGNAL( triggered() ), mainWindow(), SLOT( slotAddLocation() ) );
489 action
= new KAction( i18n( "Toggle Playlist Window" ), mainWindow() );
490 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_P
) );
491 connect( action
, SIGNAL( triggered() ), mainWindow(), SLOT( showHide() ) );
494 action
= new KAction( i18n( "Show OSD" ), mainWindow() );
495 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_O
) );
496 connect( action
, SIGNAL( triggered() ), Amarok::OSD::instance(), SLOT( forceToggleOSD() ) );
498 action
= new KAction( i18n( "Mute Volume" ), mainWindow() );
499 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_M
) );
500 connect( action
, SIGNAL( triggered() ), ec
, SLOT( mute() ) );
502 action
= new KAction( i18n( "Rate Current Track: 1" ), mainWindow() );
503 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_1
) );
504 connect( action
, SIGNAL( triggered() ), SLOT( setRating1() ) );
506 action
= new KAction( i18n( "Rate Current Track: 2" ), mainWindow() );
507 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_2
) );
508 connect( action
, SIGNAL( triggered() ), SLOT( setRating2() ) );
510 action
= new KAction( i18n( "Rate Current Track: 3" ), mainWindow() );
511 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_3
) );
512 connect( action
, SIGNAL( triggered() ), SLOT( setRating3() ) );
514 action
= new KAction( i18n( "Rate Current Track: 4" ), mainWindow() );
515 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_4
) );
516 connect( action
, SIGNAL( triggered() ), SLOT( setRating4() ) );
517 action
= new KAction( i18n( "Rate Current Track: 5" ), mainWindow() );
518 action
->setGlobalShortcut( KShortcut( Qt::META
+ Qt::Key_5
) );
519 connect( action
, SIGNAL( triggered() ), SLOT( setRating5() ) );
522 // KGlobalAccel::self()->setConfigGroup( "Shortcuts" );
523 // KGlobalAccel::self()->readSettings( KGlobal::config().data() );
528 /////////////////////////////////////////////////////////////////////////////////////
530 /////////////////////////////////////////////////////////////////////////////////////
532 #include <id3v1tag.h>
533 #include <tbytevector.h>
534 #include <QTextCodec>
537 //this class is only used in this module, so I figured I may as well define it
538 //here and save creating another header/source file combination
540 class ID3v1StringHandler
: public TagLib::ID3v1::StringHandler
544 virtual TagLib::String
parse( const TagLib::ByteVector
&data
) const
546 return QStringToTString( m_codec
->toUnicode( data
.data(), data
.size() ) );
549 virtual TagLib::ByteVector
render( const TagLib::String
&ts
) const
551 const QByteArray qcs
= m_codec
->fromUnicode( TStringToQString(ts
) );
552 return TagLib::ByteVector( qcs
, (uint
) qcs
.length() );
556 ID3v1StringHandler( int codecIndex
)
557 : m_codec( QTextCodec::codecForName( QTextCodec::availableCodecs().at( codecIndex
) ) )
559 debug() << "codec: " << m_codec
;
560 debug() << "codec-name: " << m_codec
->name();
563 ID3v1StringHandler( QTextCodec
*codec
)
566 debug() << "codec: " << m_codec
;
567 debug() << "codec-name: " << m_codec
->name();
570 virtual ~ID3v1StringHandler()
575 void App::applySettings( bool firstTime
)
577 ///Called when the configDialog is closed with OK or Apply
582 //probably needs to be done in TrayIcon when it receives a QEvent::ToolTip (see QSystemtrayIcon documentation)
583 //TrackToolTip::instance()->removeFromWidget( m_tray );
585 Scrobbler::instance()->applySettings();
586 Amarok::OSD::instance()->applySettings();
587 CollectionDB::instance()->applySettings();
589 m_tray
->setVisible( AmarokConfig::showTrayIcon() );
590 //TrackToolTip::instance()->addToWidget( m_tray );
594 //on startup we need to show the window, but only if it wasn't hidden on exit
595 //and always if the trayicon isn't showing
596 QWidget
* main_window
= mainWindow();
598 if( ( main_window
&& firstTime
&& !Amarok::config().readEntry( "HiddenOnExit", false ) ) || ( main_window
&& !AmarokConfig::showTrayIcon() ) )
603 //takes longer but feels shorter. Crazy eh? :)
604 kapp
->processEvents( QEventLoop::ExcludeUserInputEvents
);
609 EngineBase
*engine
= EngineController::engine();
611 if( firstTime
|| AmarokConfig::soundSystem() !=
612 PluginManager::getService( engine
)->property( "X-KDE-Amarok-name" ).toString() )
614 //will unload engine for us first if necessary
615 engine
= EngineController::instance()->loadEngine();
618 engine
->setXfadeLength( AmarokConfig::crossfade() ? AmarokConfig::crossfadeLength() : 0 );
619 engine
->setVolume( AmarokConfig::masterVolume() );
621 engine
->setEqualizerEnabled( AmarokConfig::equalizerEnabled() );
622 if ( AmarokConfig::equalizerEnabled() )
623 engine
->setEqualizerParameters( AmarokConfig::equalizerPreamp(), AmarokConfig::equalizerGains() );
625 Amarok::actionCollection()->action("play_audiocd")->setEnabled( EngineController::hasEngineProperty( "HasKIO" ) || EngineController::hasEngineProperty("HasCDDA"));
629 //PORT 2.0 CollectionView::instance()->renderView(true);
632 //PORT 2.0 ContextBrowser::instance()->renderView();
635 { // delete unneeded cover images from cache
636 const QString size
= QString::number( AmarokConfig::coverPreviewSize() ) + '@';
637 const QDir cacheDir
= Amarok::saveLocation( "albumcovers/cache/" );
638 const QStringList obsoleteCovers
= cacheDir
.entryList( QStringList("*") );
639 foreach( const QString
&it
, obsoleteCovers
)
640 if ( !it
.startsWith( size
) && !it
.startsWith( "50@" ) )
641 QFile( cacheDir
.filePath( it
) ).remove();
645 // Bizarrely and ironically calling this causes crashes for
646 // some people! FIXME
647 //AmarokConfig::self()->writeConfig();
656 const KCmdLineArgs
* const args
= KCmdLineArgs::parsedArgs();
657 bool restoreSession
= args
->count() == 0 || args
->isSet( "append" ) || args
->isSet( "enqueue" )
658 || Amarok::config().readEntry( "AppendAsDefault", false );
660 // Make this instance so it can start receiving signals
661 MoodServer::instance();
663 // Remember old folder setup, so we can detect changes after the wizard was used
664 //const QStringList oldCollectionFolders = MountPointManager::instance()->collectionFolders();
667 // Is this needed in Amarok 2?
668 if( Amarok::config().readEntry( "First Run", true ) || args
->isSet( "wizard" ) )
670 std::cout
<< "STARTUP\n" << std::flush
; //hide the splashscreen
671 Amarok::config().writeEntry( "First Run", false );
672 Amarok::config().sync();
675 //CollectionDB::instance()->checkDatabase();
677 m_mainWindow
= new MainWindow();
679 m_tray
= new Amarok::TrayIcon( mainWindow() );
681 mainWindow()->init(); //creates the playlist, browsers, etc.
682 //FIXME: we shouldn't have to do this.
683 pApp
->mainWindow()->show();
684 //DON'T DELETE THIS NEXT LINE or the app crashes when you click the X (unless we reimplement closeEvent)
685 //Reason: in ~App we have to call the deleteBrowsers method or else we run afoul of refcount foobar in KHTMLPart
686 //But if you click the X (not Action->Quit) it automatically kills MainWindow because KMainWindow sets this
687 //for us as default (bad KMainWindow)
688 mainWindow()->setAttribute( Qt::WA_DeleteOnClose
, false );
689 //init playlist window as soon as the database is guaranteed to be usable
690 //connect( CollectionDB::instance(), SIGNAL( databaseUpdateDone() ), mainWindow(), SLOT( init() ) );
692 // FIXME: something is broken with global shortcuts & windows,
693 // dbus call is causing a ~30 second timeout. Disable for now.
695 initGlobalShortcuts();
697 //load previous playlist in separate thread
698 //FIXME: causes a lot of breakage due to the collection not being properly initialized at startup.
699 //Reenable when fixed.
700 // if ( restoreSession && AmarokConfig::savePlaylist() )
702 // The::playlistModel()->restoreSession();
704 if( args
->isSet( "engine" ) ) {
705 // we correct some common errors (case issues, missing -engine off the end)
706 QString engine
= args
->getOption( "engine" ).toLower();
707 if( engine
.startsWith( "gstreamer" ) ) engine
= "gst-engine";
708 if( !engine
.endsWith( "engine" ) ) engine
+= "-engine";
710 AmarokConfig::setSoundSystem( engine
);
713 //create engine, show TrayIcon etc.
714 applySettings( true );
716 // Start ScriptManager. Must be created _after_ MainWindow.
717 ScriptManager::instance();
720 //do after applySettings(), or the OSD will flicker and other wierdness!
721 //do before restoreSession()!
722 EngineController::instance()->attach( this );
724 //set a default interface
725 engineStateChanged( Engine::Empty
);
727 if ( AmarokConfig::resumePlayback() && restoreSession
&& !args
->isSet( "stop" ) ) {
728 //restore session as long as the user didn't specify media to play etc.
729 //do this after applySettings() so OSD displays correctly
730 EngineController::instance()->restoreSession();
733 // Refetch covers every 80 days to comply with Amazon license
736 CollectionDB
*collDB
= CollectionDB::instance();
738 if ( AmarokConfig::monitorChanges() )
739 //connect( collDB, SIGNAL( databaseUpdateDone() ), collDB, SLOT( scanModifiedDirs() ) );
740 CollectionManager::instance()->checkCollectionChanges();
749 void App::engineStateChanged( Engine::State state
, Engine::State oldState
)
751 Meta::TrackPtr track
= EngineController::instance()->currentTrack();
752 //track is 0 if the engien state is Empty. we check that in the switch
756 mainWindow()->setCaption( "Amarok" );
757 TrackToolTip::instance()->clear();
758 Amarok::OSD::instance()->setImage( QImage( KIconLoader().iconPath( "amarok", -KIconLoader::SizeHuge
) ) );
761 case Engine::Playing
:
762 if ( oldState
== Engine::Paused
)
763 Amarok::OSD::instance()->OSDWidget::show( i18nc( "state, as in playing", "Play" ) );
764 if ( !track
->prettyName().isEmpty() )
765 //TODO: write a utility function somewhere
766 mainWindow()->setCaption( i18n("Amarok - %1", /*bundle.veryNiceTitle()*/ track
->prettyName() ) );
770 Amarok::OSD::instance()->OSDWidget::show( i18n("Paused") );
774 mainWindow()->setCaption( "Amarok" );
782 void App::engineNewMetaData( const MetaBundle
&bundle
, bool /*trackChanged*/ )
784 Amarok::OSD::instance()->show( bundle
);
785 if ( !bundle
.prettyTitle().isEmpty() )
786 mainWindow()->setCaption( i18n("Amarok - %1", bundle
.veryNiceTitle() ) );
788 TrackToolTip::instance()->setTrack( bundle
);
791 void App::engineTrackPositionChanged( long position
, bool /*userSeek*/ )
793 TrackToolTip::instance()->setPos( position
);
796 void App::engineVolumeChanged( int newVolume
)
798 Amarok::OSD::instance()->OSDWidget::volChanged( newVolume
);
801 void App::slotConfigEqualizer() //SLOT
803 EqualizerSetup::instance()->show();
804 EqualizerSetup::instance()->raise();
808 void App::slotConfigAmarok( const QByteArray
& page
)
810 DEBUG_THREAD_FUNC_INFO
812 Amarok2ConfigDialog
* dialog
= static_cast<Amarok2ConfigDialog
*>( KConfigDialog::exists( "settings" ) );
816 //KConfigDialog didn't find an instance of this dialog, so lets create it :
817 dialog
= new Amarok2ConfigDialog( mainWindow(), "settings", AmarokConfig::self() );
819 connect( dialog
, SIGNAL(settingsChanged(const QString
&)), SLOT(applySettings()) );
822 //FIXME it seems that if the dialog is on a different desktop it gets lost
823 // what do to? detect and move it?
825 // if ( page.isNull() )
827 // dialog->showPage( AmarokConfigDialog::s_currentPage );
829 dialog
->showPageByName( page
);
833 dialog
->activateWindow();
836 void App::slotConfigShortcuts()
838 KShortcutsDialog::configure( Amarok::actionCollection(), KShortcutsEditor::LetterShortcutsAllowed
, mainWindow() );
841 void App::slotConfigToolBars()
843 KEditToolBar
dialog( mainWindow()->actionCollection(), mainWindow() );
844 dialog
.setResourceFile( mainWindow()->xmlFile() );
846 dialog
.showButton( KEditToolBar::Apply
, false );
848 // if( dialog.exec() )
850 // mainWindow()->reloadXML();
851 // mainWindow()->createGUI();
855 void App::setUseScores( bool use
)
857 AmarokConfig::setUseScores( use
);
858 emit
useScores( use
);
861 void App::setUseRatings( bool use
)
863 AmarokConfig::setUseRatings( use
);
864 emit
useRatings( use
);
867 void App::setMoodbarPrefs( bool show
, bool moodier
, int alter
, bool withMusic
)
869 AmarokConfig::setShowMoodbar( show
);
870 AmarokConfig::setMakeMoodier( moodier
);
871 AmarokConfig::setAlterMood( alter
);
872 AmarokConfig::setMoodsWithMusic( withMusic
);
873 emit
moodbarPrefs( show
, moodier
, alter
, withMusic
);
876 KIO::Job
*App::trashFiles( const KUrl::List
&files
)
878 KIO::Job
*job
= KIO::trash( files
);
879 Amarok::ContextStatusBar::instance()->newProgressOperation( job
).setDescription( i18n("Moving files to trash") );
880 connect( job
, SIGNAL( result( KJob
* ) ), this, SLOT( slotTrashResult( KJob
* ) ) );
884 void App::setRating( int n
)
886 if( !AmarokConfig::useRatings() ) return;
890 const Engine::State s
= EngineController::instance()->engine()->state();
891 if( s
== Engine::Playing
|| s
== Engine::Paused
|| s
== Engine::Idle
)
893 Meta::TrackPtr track
= EngineController::instance()->currentTrack();
894 track
->setRating( n
);
895 Amarok::OSD::instance()->OSDWidget::ratingChanged( track
->rating() );
898 // else if( MainWindow::self()->isReallyShown() && Playlist::instance()->qscrollview()->hasFocus() )
899 // Playlist::instance()->setSelectedRatings( n );
902 void App::slotTrashResult( KJob
*job
)
905 job
->uiDelegate()->showErrorMessage();
910 emit
prepareToQuit();
911 if( MediaBrowser::instance() && MediaBrowser::instance()->blockQuit() )
913 // don't quit yet, as some media devices still have to finish transferring data
914 QTimer::singleShot( 100, this, SLOT( quit() ) );
917 KApplication::quit();
924 QWidget
*mainWindow()
926 return pApp
->mainWindow();
929 KActionCollection
*actionCollection()
931 return pApp
->mainWindow()->actionCollection();
934 KConfigGroup
config( const QString
&group
)
936 //Slightly more useful config() that allows setting the group simultaneously
937 return KGlobal::config()->group( group
);
940 bool invokeBrowser( const QString
& url
)
942 //URL can be in whatever forms KUrl understands - ie most.
943 const QString cmd
= KShell::quoteArg(AmarokConfig::externalBrowser())
944 + ' ' + KShell::quoteArg(KUrl( url
).url());
945 return KRun::runCommand( cmd
, 0L ) > 0;
948 namespace ColorScheme
957 OverrideCursor::OverrideCursor( Qt::CursorShape cursor
)
959 QApplication::setOverrideCursor( cursor
== Qt::WaitCursor
?
964 OverrideCursor::~OverrideCursor()
966 QApplication::restoreOverrideCursor();
969 QString
saveLocation( const QString
&directory
)
971 globalDirsMutex
.lock();
972 QString result
= KGlobal::dirs()->saveLocation( "data", QString("amarok/") + directory
, true );
973 globalDirsMutex
.unlock();
977 QString
cleanPath( const QString
&path
)
979 QString result
= path
;
981 result
.replace( QChar(0x00e4), "ae" ).replace( QChar(0x00c4), "Ae" );
982 result
.replace( QChar(0x00f6), "oe" ).replace( QChar(0x00d6), "Oe" );
983 result
.replace( QChar(0x00fc), "ue" ).replace( QChar(0x00dc), "Ue" );
984 result
.replace( QChar(0x00df), "ss" );
986 // some strange accents
987 result
.replace( QChar(0x00e7), "c" ).replace( QChar(0x00c7), "C" );
988 result
.replace( QChar(0x00fd), "y" ).replace( QChar(0x00dd), "Y" );
989 result
.replace( QChar(0x00f1), "n" ).replace( QChar(0x00d1), "N" );
991 // czech letters with carons
992 result
.replace( QChar(0x0161), "s" ).replace( QChar(0x0160), "S" );
993 result
.replace( QChar(0x010d), "c" ).replace( QChar(0x010c), "C" );
994 result
.replace( QChar(0x0159), "r" ).replace( QChar(0x0158), "R" );
995 result
.replace( QChar(0x017e), "z" ).replace( QChar(0x017d), "Z" );
996 result
.replace( QChar(0x0165), "t" ).replace( QChar(0x0164), "T" );
997 result
.replace( QChar(0x0148), "n" ).replace( QChar(0x0147), "N" );
998 result
.replace( QChar(0x010f), "d" ).replace( QChar(0x010e), "D" );
1001 QChar a
[] = { 'a', 0xe0,0xe1,0xe2,0xe3,0xe5, 0 };
1002 QChar A
[] = { 'A', 0xc0,0xc1,0xc2,0xc3,0xc5, 0 };
1003 QChar E
[] = { 'e', 0xe8,0xe9,0xea,0xeb,0x11a, 0 };
1004 QChar e
[] = { 'E', 0xc8,0xc9,0xca,0xcb,0x11b, 0 };
1005 QChar i
[] = { 'i', 0xec,0xed,0xee,0xef, 0 };
1006 QChar I
[] = { 'I', 0xcc,0xcd,0xce,0xcf, 0 };
1007 QChar o
[] = { 'o', 0xf2,0xf3,0xf4,0xf5,0xf8, 0 };
1008 QChar O
[] = { 'O', 0xd2,0xd3,0xd4,0xd5,0xd8, 0 };
1009 QChar u
[] = { 'u', 0xf9,0xfa,0xfb,0x16e, 0 };
1010 QChar U
[] = { 'U', 0xd9,0xda,0xdb,0x16f, 0 };
1011 QChar nul
[] = { 0 };
1012 QChar
*replacements
[] = { a
, A
, e
, E
, i
, I
, o
, O
, u
, U
, nul
};
1014 for( int i
= 0; i
< result
.length(); i
++ )
1016 QChar c
= result
[ i
];
1017 for( uint n
= 0; replacements
[n
][0] != QChar(0); n
++ )
1019 for( uint k
=0; replacements
[n
][k
] != QChar(0); k
++ )
1021 if( replacements
[n
][k
] == c
)
1023 c
= replacements
[n
][0];
1032 QString
asciiPath( const QString
&path
)
1034 QString result
= path
;
1035 for( int i
= 0; i
< result
.length(); i
++ )
1037 QChar c
= result
[ i
];
1038 if( c
> QChar(0x7f) || c
== QChar(0) )
1047 QString
vfatPath( const QString
&path
)
1051 for( int i
= 0; i
< s
.length(); i
++ )
1055 || c
=='*' || c
=='?' || c
=='<' || c
=='>'
1056 || c
=='|' || c
=='"' || c
==':' || c
=='/'
1062 uint len
= s
.length();
1063 if( len
== 3 || (len
> 3 && s
[3] == '.') )
1065 QString l
= s
.left(3).toLower();
1066 if( l
=="aux" || l
=="con" || l
=="nul" || l
=="prn" )
1069 else if( len
== 4 || (len
> 4 && s
[4] == '.') )
1071 QString l
= s
.left(3).toLower();
1072 QString d
= s
.mid(3,1);
1073 if( (l
=="com" || l
=="lpt") &&
1074 (d
=="0" || d
=="1" || d
=="2" || d
=="3" || d
=="4" ||
1075 d
=="5" || d
=="6" || d
=="7" || d
=="8" || d
=="9") )
1079 while( s
.startsWith( '.' ) )
1082 while( s
.endsWith( '.' ) )
1083 s
= s
.left( s
.length()-1 );
1087 if( s
[len
-1] == ' ' )
1093 QString
decapitateString( const QString
&input
, const QString
&ref
)
1095 QString t
= ref
.toUpper();
1096 int length
= t
.length();
1097 int commonLength
= 0;
1100 if ( input
.toUpper().startsWith( t
) )
1102 commonLength
= t
.length();
1103 t
= ref
.toUpper().left( t
.length() + length
/2 );
1108 t
= ref
.toUpper().left( t
.length() - length
/2 );
1112 QString clean
= input
;
1113 if( t
.endsWith( ' ' ) || !ref
.at( t
.length() ).isLetterOrNumber() ) // common part ends with a space or complete word
1114 clean
= input
.right( input
.length() - commonLength
).trimmed();
1118 void setUseScores( bool use
) { App::instance()->setUseScores( use
); }
1119 void setUseRatings( bool use
) { App::instance()->setUseRatings( use
); }
1120 void setMoodbarPrefs( bool show
, bool moodier
, int alter
, bool withMusic
)
1121 { App::instance()->setMoodbarPrefs( show
, moodier
, alter
, withMusic
); }
1122 KIO::Job
*trashFiles( const KUrl::List
&files
) { return App::instance()->trashFiles( files
); }