add mp3 and ogg torrent url info to JamendoAlbum
[amarok.git] / src / app.cpp
blob7dadc19df907941f8e6044ffb14e204b055ed185
1 /***************************************************************************
2 app.cpp - description
3 -------------------
4 begin : Mit Okt 23 14:35:18 CEST 2002
5 copyright : (C) 2002 by Mark Kretschmann
6 email : markey@web.de
7 ***************************************************************************/
9 /***************************************************************************
10 * *
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. *
15 * *
16 ***************************************************************************/
17 #include "app.h"
19 #include "amarok.h"
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 "debug.h"
28 #include "enginebase.h"
29 #include "enginecontroller.h"
30 #include "equalizersetup.h"
31 #include "MainWindow.h"
32 #include "mediabrowser.h"
33 #include "meta.h"
34 #include "metabundle.h"
35 #include "mountpointmanager.h"
36 #include "osd.h"
37 #include "playlist/PlaylistModel.h"
39 #include "pluginmanager.h"
40 #include "refreshimages.h"
41 #include "scriptmanager.h"
42 #include "scrobbler.h"
43 #include "statusbar.h"
44 #include "systray.h"
45 #include "threadmanager.h"
46 #include "tracktooltip.h" //engineNewMetaData()
47 #include "TheInstances.h"
48 #include "metadata/tplugins.h"
50 #include <iostream>
52 #include <KAboutData>
53 #include <KAction>
54 #include <KCmdLineArgs> //initCliArgs()
55 #include <KConfigDialogManager>
56 #include <KCursor> //Amarok::OverrideCursor
57 #include <KEditToolBar> //slotConfigToolbars()
58 #include <KGlobalAccel> //initGlobalShortcuts()
59 #include <KGlobalSettings> //applyColorScheme()
60 #include <KIO/CopyJob>
61 #include <KIconLoader> //amarok Icon
62 #include <KJob>
63 #include <KJobUiDelegate>
64 #include <KLocale>
65 #include <KMessageBox> //applySettings(), genericEventHandler()
66 #include <KRun> //Amarok::invokeBrowser()
67 #include <kshell.h>
68 #include <KShortcutsDialog> //slotConfigShortcuts()
69 #include <KSplashScreen>
70 #include <KStandardDirs>
72 #include <QCloseEvent>
73 #include <QDBusInterface>
74 #include <QDBusReply>
75 #include <QEvent> //genericEventHandler()
76 #include <QEventLoop> //applySettings()
77 #include <QFile>
78 #include <QPixmapCache>
79 #include <Q3PopupMenu> //genericEventHandler
80 #include <QTimer> //showHyperThreadingWarning()
81 #include <QToolTip> //default tooltip for trayicon
83 QMutex Debug::mutex;
84 QMutex Amarok::globalDirsMutex;
86 int App::mainThreadId = 0;
88 #ifdef Q_WS_MAC
89 #include <CoreFoundation/CoreFoundation.h>
90 extern void setupEventHandler_mac(long);
91 #endif
93 AMAROK_EXPORT KAboutData aboutData( "amarok", 0,
94 ki18n( "Amarok" ), APP_VERSION,
95 ki18n( "The audio player for KDE" ), KAboutData::License_GPL,
96 ki18n( "(C) 2002-2003, Mark Kretschmann\n(C) 2003-2007, The Amarok Development Squad" ),
97 ki18n( "IRC:\nirc.freenode.net - #amarok, #amarok.de, #amarok.es\n\nFeedback:\namarok@kde.org\n\n(Build Date: " __DATE__ ")" ),
98 ( "http://amarok.kde.org" ) );
101 App::App()
102 : KUniqueApplication()
103 , m_splash( 0 )
105 DEBUG_BLOCK
107 if( AmarokConfig::showSplashscreen() )
109 QPixmap splashpix( KStandardDirs().findResource("data", "amarok/images/splash_screen.jpg") );
110 m_splash = new KSplashScreen( splashpix, Qt::WindowStaysOnTopHint );
111 m_splash->show();
114 registerTaglibPlugins();
116 qRegisterMetaType<MetaBundle>();
118 qRegisterMetaType<Meta::DataPtr>();
119 qRegisterMetaType<Meta::DataList>();
120 qRegisterMetaType<Meta::TrackPtr>();
121 qRegisterMetaType<Meta::TrackList>();
122 qRegisterMetaType<Meta::AlbumPtr>();
123 qRegisterMetaType<Meta::AlbumList>();
124 qRegisterMetaType<Meta::ArtistPtr>();
125 qRegisterMetaType<Meta::ArtistList>();
126 qRegisterMetaType<Meta::GenrePtr>();
127 qRegisterMetaType<Meta::GenreList>();
128 qRegisterMetaType<Meta::ComposerPtr>();
129 qRegisterMetaType<Meta::ComposerList>();
130 qRegisterMetaType<Meta::YearPtr>();
131 qRegisterMetaType<Meta::YearList>();
134 //make sure we have enough cache space for all our crazy svg stuff
135 QPixmapCache::setCacheLimit ( 20 * 1024 );
137 #ifdef Q_WS_MAC
138 // this is inspired by OpenSceneGraph: osgDB/FilePath.cpp
140 // Start with the the Bundle PlugIns directory.
142 // Get the main bundle first. No need to retain or release it since
143 // we are not keeping a reference
144 CFBundleRef myBundle = CFBundleGetMainBundle();
145 if( myBundle )
147 // CFBundleGetMainBundle will return a bundle ref even if
148 // the application isn't part of a bundle, so we need to
149 // check
150 // if the path to the bundle ends in ".app" to see if it is
151 // a
152 // proper application bundle. If it is, the plugins path is
153 // added
154 CFURLRef urlRef = CFBundleCopyBundleURL(myBundle);
155 if(urlRef)
157 char bundlePath[1024];
158 if( CFURLGetFileSystemRepresentation( urlRef, true, (UInt8 *)bundlePath, sizeof(bundlePath) ) )
160 QByteArray bp( bundlePath );
161 size_t len = bp.length();
162 if( len > 4 && bp.right( 4 ) == ".app" )
164 bp.append( "/Contents/MacOS" );
165 QByteArray path = getenv( "PATH" );
166 if( path.length() > 0 )
168 path.prepend( ":" );
170 path.prepend( bp );
171 debug() << "setting PATH=" << path;
172 setenv("PATH", path, 1);
175 // docs say we are responsible for releasing CFURLRef
176 CFRelease(urlRef);
179 #endif
181 //needs to be created before the wizard
182 new Amarok::DbusPlayerHandler(); // Must be created first
183 new Amarok::DbusPlaylistHandler();
184 new Amarok::DbusPlaylistBrowserHandler();
185 new Amarok::DbusContextHandler();
186 new Amarok::DbusCollectionHandler();
187 // new Amarok::DbusMediaBrowserHandler();
188 new Amarok::DbusScriptHandler();
190 // tell AtomicString that this is the GUI thread
191 if ( !AtomicString::isMainThread() )
192 qWarning("AtomicString was initialized from a thread other than the GUI "
193 "thread. This could lead to memory leaks.");
195 #ifdef Q_WS_MAC
196 setupEventHandler_mac((long)this);
197 #endif
198 QDBusConnection::sessionBus().registerService("org.kde.amarok");
199 QTimer::singleShot( 0, this, SLOT( continueInit() ) );
202 App::~App()
204 DEBUG_BLOCK
206 delete m_splash;
207 m_splash = 0;
209 // Hiding the OSD before exit prevents crash
210 Amarok::OSD::instance()->hide();
212 EngineBase* const engine = EngineController::engine();
214 if ( AmarokConfig::resumePlayback() ) {
215 if ( engine->state() != Engine::Empty ) {
216 AmarokConfig::setResumeTrack( EngineController::instance()->playingURL().prettyUrl() );
217 AmarokConfig::setResumeTime( engine->position() );
219 else AmarokConfig::setResumeTrack( QString() ); //otherwise it'll play previous resume next time!
222 EngineController::instance()->endSession(); //records final statistics
223 EngineController::instance()->detach( this );
225 // do even if trayicon is not shown, it is safe
226 Amarok::config().writeEntry( "HiddenOnExit", mainWindow()->isHidden() );
228 CollectionDB::instance()->stopScan();
230 ThreadManager::deleteInstance(); //waits for jobs to finish
232 // delete mainWindow();
234 // this must be deleted before the connection to the Xserver is
235 // severed, or we risk a crash when the QApplication is exited,
236 // I asked Trolltech! *smug*
237 delete Amarok::OSD::instance();
239 AmarokConfig::setVersion( APP_VERSION );
240 AmarokConfig::self()->writeConfig();
242 //need to unload the engine before the kapplication is destroyed
243 PluginManager::unload( engine );
247 #include <QStringList>
249 namespace
251 // grabbed from KsCD source, kompatctdisk.cpp
252 QString urlToDevice(const QString& device)
254 KUrl deviceUrl(device);
255 if (deviceUrl.protocol() == "media" || deviceUrl.protocol() == "system")
257 debug() << "WARNING: urlToDevice needs to be reimplemented with KDE4 technology, it's just a stub at the moment";
258 QDBusInterface mediamanager( "org.kde.kded", "/modules/mediamanager", "org.kde.MediaManager" );
259 QDBusReply<QStringList> reply = mediamanager.call( "properties",deviceUrl.fileName() );
260 if (!reply.isValid()) {
261 debug() << "Invalid reply from mediamanager";
262 return QString();
264 QStringList properties = reply;
265 if( properties.count()< 6 )
266 return QString();
267 debug() << "Reply from mediamanager " << properties[5];
268 return properties[5];
271 return device;
277 void App::handleCliArgs() //static
279 KCmdLineArgs* const args = KCmdLineArgs::parsedArgs();
281 if ( args->isSet( "cwd" ) )
283 KCmdLineArgs::setCwd( args->getOption( "cwd" ).toLocal8Bit() );
286 bool haveArgs = false;
287 if ( args->count() > 0 )
289 haveArgs = true;
291 KUrl::List list;
292 for( int i = 0; i < args->count(); i++ )
294 KUrl url = args->url( i );
295 //TODO:PORTME
296 // if( url.protocol() == "itpc" || url.protocol() == "pcast" )
297 // PlaylistBrowserNS::instance()->addPodcast( url );
298 // else
299 list << url;
302 int options = Playlist::AppendAndPlay;
303 if( args->isSet( "queue" ) )
304 options = Playlist::Queue;
305 else if( args->isSet( "append" ) || args->isSet( "enqueue" ) )
306 options = Playlist::Append;
307 else if( args->isSet( "load" ) )
308 options = Playlist::Replace;
310 if( args->isSet( "play" ) )
311 options |= Playlist::DirectPlay;
313 The::playlistModel()->insertMedia( list, options );
316 //we shouldn't let the user specify two of these since it is pointless!
317 //so we prioritise, pause > stop > play > next > prev
318 //thus pause is the least destructive, followed by stop as brakes are the most important bit of a car(!)
319 //then the others seemed sensible. Feel free to modify this order, but please leave justification in the cvs log
320 //I considered doing some sanity checks (eg only stop if paused or playing), but decided it wasn't worth it
321 else if ( args->isSet( "pause" ) )
323 haveArgs = true;
324 EngineController::instance()->pause();
326 else if ( args->isSet( "stop" ) )
328 haveArgs = true;
329 EngineController::instance()->stop();
331 else if ( args->isSet( "play-pause" ) )
333 haveArgs = true;
334 EngineController::instance()->playPause();
336 else if ( args->isSet( "play" ) ) //will restart if we are playing
338 haveArgs = true;
339 EngineController::instance()->play();
341 else if ( args->isSet( "next" ) )
343 haveArgs = true;
344 EngineController::instance()->next();
346 else if ( args->isSet( "previous" ) )
348 haveArgs = true;
349 EngineController::instance()->previous();
351 else if (args->isSet("cdplay"))
353 haveArgs = true;
354 QString device = args->getOption("cdplay");
355 KUrl::List urls;
356 if (EngineController::engine()->getAudioCDContents(device, urls)) {
357 Meta::TrackList tracks = CollectionManager::instance()->tracksForUrls( urls );
358 The::playlistModel()->insertOptioned(
359 tracks, Playlist::Replace|Playlist::DirectPlay);
360 } else { // Default behaviour
361 debug() <<
362 "Sorry, the engine doesn't support direct play from AudioCD..."
367 if ( args->isSet( "toggle-playlist-window" ) )
369 haveArgs = true;
370 pApp->mainWindow()->showHide();
373 Amarok::config().writeEntry( "Debug Enabled", args->isSet( "debug" ) );
375 static bool firstTime = true;
376 if( !firstTime && !haveArgs )
377 pApp->mainWindow()->activate();
378 firstTime = false;
380 args->clear(); //free up memory
384 /////////////////////////////////////////////////////////////////////////////////////
385 // INIT
386 /////////////////////////////////////////////////////////////////////////////////////
388 void App::initCliArgs( int argc, char *argv[] ) //static
390 KCmdLineArgs::reset();
391 KCmdLineArgs::init( argc, argv, &::aboutData ); //calls KCmdLineArgs::addStdCmdLineOptions()
393 KCmdLineOptions options;
394 options.add("+[URL(s)]", ki18n( "Files/URLs to open" ));
395 options.add("r");
396 options.add("previous", ki18n( "Skip backwards in playlist" ));
397 options.add("p");
398 options.add("play", ki18n( "Start playing current playlist" ));
399 options.add("t");
400 options.add("play-pause", ki18n( "Play if stopped, pause if playing" ));
401 options.add("pause", ki18n( "Pause playback" ));
402 options.add("s");
403 options.add("stop", ki18n( "Stop playback" ));
404 options.add("f");
405 options.add("next", ki18n( "Skip forwards in playlist" ));
406 options.add(":", ki18n("Additional options:"));
407 options.add("a");
408 options.add("append", ki18n( "Append files/URLs to playlist" ));
409 options.add("e");
410 options.add("enqueue", ki18n("See append, available for backwards compatability"));
411 options.add("queue", ki18n("Queue URLs after the currently playing track"));
412 options.add("l");
413 options.add("load", ki18n("Load URLs, replacing current playlist"));
414 options.add("d");
415 options.add("debug", ki18n("Print verbose debugging information"));
416 options.add("m");
417 options.add("toggle-playlist-window", ki18n("Toggle the Playlist-window"));
418 options.add("wizard", ki18n( "Run first-run wizard" ));
419 options.add("engine <name>", ki18n( "Use the <name> engine" ));
420 options.add("cwd <directory>", ki18n( "Base for relative filenames/URLs" ));
421 options.add("cdplay <device>", ki18n("Play an AudioCD from <device> or system:/media/<device>"));
422 KCmdLineArgs::addCmdLineOptions( options ); //add our own options
426 void App::initGlobalShortcuts()
428 EngineController* const ec = EngineController::instance();
429 KAction* action;
431 action = new KAction( i18n( "Play" ), mainWindow() );
432 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_X ) );
433 connect( action, SIGNAL( triggered() ), ec, SLOT( play() ) );
435 // m_pGlobalAccel->insert( "pause", i18n( "Pause" ), 0, 0, 0, ec, SLOT( pause() ), true, true );
436 action = new KAction( i18n( "Pause" ), mainWindow() );
437 connect( action, SIGNAL( triggered() ), ec, SLOT( pause() ) );
439 action = new KAction( i18n( "Play/Pause" ), mainWindow() );
440 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_C ) );
441 connect( action, SIGNAL( triggered() ), ec, SLOT( playPause() ) );
443 action = new KAction( i18n( "Stop" ), mainWindow() );
444 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_V ) );
445 connect( action, SIGNAL( triggered() ), ec, SLOT( stop() ) );
447 // 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 );
448 // action = new KAction( i18n( "Stop Playing After Current Track" ), mainWindow() );
449 // action->setGlobalShortcut( KShortcut( Qt::META + Qt::CTRL + Qt::Key_V ) );
450 //Port 2.0
451 // connect( action, SIGNAL( triggered() ), Playlist::instance()->qscrollview(), SLOT( toggleStopAfterCurrentTrack() ) );
453 action = new KAction( i18n( "Next Track" ), mainWindow() );
454 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_B ) );
455 connect( action, SIGNAL( triggered() ), The::playlistModel(), SLOT( next() ) );
457 action = new KAction( i18n( "Previous Track" ), mainWindow() );
458 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_Z ) );
459 connect( action, SIGNAL( triggered() ), The::playlistModel(), SLOT( previous() ) );
461 action = new KAction( i18n( "Increase Volume" ), mainWindow() );
462 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_Plus ) );
463 connect( action, SIGNAL( triggered() ), ec, SLOT( increaseVolume() ) );
465 action = new KAction( i18n( "Decrease Volume" ), mainWindow() );
466 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_Minus ) );
467 connect( action, SIGNAL( triggered() ), ec, SLOT( decreaseVolume() ) );
470 action = new KAction( i18n( "Seek Forward" ), mainWindow() );
471 action->setGlobalShortcut( KShortcut( Qt::META + Qt::SHIFT + Qt::Key_Plus ) );
472 connect( action, SIGNAL( triggered() ), ec, SLOT( seekForward() ) );
475 action = new KAction( i18n( "Seek Backward" ), mainWindow() );
476 action->setGlobalShortcut( KShortcut( Qt::META + Qt::SHIFT + Qt::Key_Minus ) );
477 connect( action, SIGNAL( triggered() ), ec, SLOT( seekBackward() ) );
479 action = new KAction( i18n( "Add Media..." ), mainWindow() );
480 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_A ) );
481 connect( action, SIGNAL( triggered() ), mainWindow(), SLOT( slotAddLocation() ) );
483 action = new KAction( i18n( "Toggle Playlist Window" ), mainWindow() );
484 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_P ) );
485 connect( action, SIGNAL( triggered() ), mainWindow(), SLOT( showHide() ) );
488 action = new KAction( i18n( "Show OSD" ), mainWindow() );
489 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_O ) );
490 connect( action, SIGNAL( triggered() ), Amarok::OSD::instance(), SLOT( forceToggleOSD() ) );
492 action = new KAction( i18n( "Mute Volume" ), mainWindow() );
493 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_M ) );
494 connect( action, SIGNAL( triggered() ), ec, SLOT( mute() ) );
496 action = new KAction( i18n( "Rate Current Track: 1" ), mainWindow() );
497 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_1 ) );
498 connect( action, SIGNAL( triggered() ), SLOT( setRating1() ) );
500 action = new KAction( i18n( "Rate Current Track: 2" ), mainWindow() );
501 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_2 ) );
502 connect( action, SIGNAL( triggered() ), SLOT( setRating2() ) );
504 action = new KAction( i18n( "Rate Current Track: 3" ), mainWindow() );
505 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_3 ) );
506 connect( action, SIGNAL( triggered() ), SLOT( setRating3() ) );
508 action = new KAction( i18n( "Rate Current Track: 4" ), mainWindow() );
509 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_4 ) );
510 connect( action, SIGNAL( triggered() ), SLOT( setRating4() ) );
511 action = new KAction( i18n( "Rate Current Track: 5" ), mainWindow() );
512 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_5 ) );
513 connect( action, SIGNAL( triggered() ), SLOT( setRating5() ) );
516 // KGlobalAccel::self()->setConfigGroup( "Shortcuts" );
517 // KGlobalAccel::self()->readSettings( KGlobal::config().data() );
522 /////////////////////////////////////////////////////////////////////////////////////
523 // METHODS
524 /////////////////////////////////////////////////////////////////////////////////////
526 #include <id3v1tag.h>
527 #include <tbytevector.h>
528 #include <QTextCodec>
529 #include <KGlobal>
531 //this class is only used in this module, so I figured I may as well define it
532 //here and save creating another header/source file combination
534 class ID3v1StringHandler : public TagLib::ID3v1::StringHandler
536 QTextCodec *m_codec;
538 virtual TagLib::String parse( const TagLib::ByteVector &data ) const
540 return QStringToTString( m_codec->toUnicode( data.data(), data.size() ) );
543 virtual TagLib::ByteVector render( const TagLib::String &ts ) const
545 const QByteArray qcs = m_codec->fromUnicode( TStringToQString(ts) );
546 return TagLib::ByteVector( qcs, (uint) qcs.length() );
549 public:
550 ID3v1StringHandler( int codecIndex )
551 : m_codec( QTextCodec::codecForName( QTextCodec::availableCodecs().at( codecIndex ) ) )
553 debug() << "codec: " << m_codec;
554 debug() << "codec-name: " << m_codec->name();
557 ID3v1StringHandler( QTextCodec *codec )
558 : m_codec( codec )
560 debug() << "codec: " << m_codec;
561 debug() << "codec-name: " << m_codec->name();
564 virtual ~ID3v1StringHandler()
568 //SLOT
569 void App::applySettings( bool firstTime )
571 ///Called when the configDialog is closed with OK or Apply
573 DEBUG_BLOCK
575 #ifndef Q_WS_MAC
576 //probably needs to be done in TrayIcon when it receives a QEvent::ToolTip (see QSystemtrayIcon documentation)
577 //TrackToolTip::instance()->removeFromWidget( m_tray );
578 #endif
579 Scrobbler::instance()->applySettings();
580 Amarok::OSD::instance()->applySettings();
581 CollectionDB::instance()->applySettings();
582 #ifndef Q_WS_MAC
583 m_tray->setVisible( AmarokConfig::showTrayIcon() );
584 //TrackToolTip::instance()->addToWidget( m_tray );
585 #endif
588 //on startup we need to show the window, but only if it wasn't hidden on exit
589 //and always if the trayicon isn't showing
590 QWidget* main_window = mainWindow();
591 #ifndef Q_WS_MAC
592 if( ( main_window && firstTime && !Amarok::config().readEntry( "HiddenOnExit", false ) ) || ( main_window && !AmarokConfig::showTrayIcon() ) )
593 #endif
595 main_window->show();
597 //takes longer but feels shorter. Crazy eh? :)
598 kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
602 { //<Engine>
603 EngineBase *engine = EngineController::engine();
605 if( firstTime || AmarokConfig::soundSystem() !=
606 PluginManager::getService( engine )->property( "X-KDE-Amarok-name" ).toString() )
608 //will unload engine for us first if necessary
609 engine = EngineController::instance()->loadEngine();
612 engine->setXfadeLength( AmarokConfig::crossfade() ? AmarokConfig::crossfadeLength() : 0 );
613 engine->setVolume( AmarokConfig::masterVolume() );
615 engine->setEqualizerEnabled( AmarokConfig::equalizerEnabled() );
616 if ( AmarokConfig::equalizerEnabled() )
617 engine->setEqualizerParameters( AmarokConfig::equalizerPreamp(), AmarokConfig::equalizerGains() );
619 Amarok::actionCollection()->action("play_audiocd")->setEnabled( EngineController::hasEngineProperty( "HasKIO" ) || EngineController::hasEngineProperty("HasCDDA"));
620 } //</Engine>
622 { //<Collection>
623 //PORT 2.0 CollectionView::instance()->renderView(true);
624 } //</Collection>
625 { //<Context>
626 //PORT 2.0 ContextBrowser::instance()->renderView();
627 } //</Context>
629 { // delete unneeded cover images from cache
630 const QString size = QString::number( AmarokConfig::coverPreviewSize() ) + '@';
631 const QDir cacheDir = Amarok::saveLocation( "albumcovers/cache/" );
632 const QStringList obsoleteCovers = cacheDir.entryList( QStringList("*") );
633 foreach( QString it, obsoleteCovers )
634 if ( !it.startsWith( size ) && !it.startsWith( "50@" ) )
635 QFile( cacheDir.filePath( it ) ).remove();
638 //if ( !firstTime )
639 // Bizarrely and ironically calling this causes crashes for
640 // some people! FIXME
641 //AmarokConfig::self()->writeConfig();
645 //SLOT
646 void
647 App::continueInit()
649 DEBUG_BLOCK
650 const KCmdLineArgs* const args = KCmdLineArgs::parsedArgs();
651 bool restoreSession = args->count() == 0 || args->isSet( "append" ) || args->isSet( "enqueue" )
652 || Amarok::config().readEntry( "AppendAsDefault", false );
654 // Make this instance so it can start receiving signals
655 MoodServer::instance();
657 // Remember old folder setup, so we can detect changes after the wizard was used
658 //const QStringList oldCollectionFolders = MountPointManager::instance()->collectionFolders();
661 // Is this needed in Amarok 2?
662 if( Amarok::config().readEntry( "First Run", true ) || args->isSet( "wizard" ) )
664 std::cout << "STARTUP\n" << std::flush; //hide the splashscreen
665 Amarok::config().writeEntry( "First Run", false );
666 Amarok::config().sync();
669 CollectionDB::instance()->checkDatabase();
671 m_mainWindow = new MainWindow();
672 #ifndef Q_WS_MAC
673 m_tray = new Amarok::TrayIcon( mainWindow() );
674 #endif
675 mainWindow()->init(); //creates the playlist, browsers, etc.
676 //init playlist window as soon as the database is guaranteed to be usable
677 //connect( CollectionDB::instance(), SIGNAL( databaseUpdateDone() ), mainWindow(), SLOT( init() ) );
678 initGlobalShortcuts();
679 //load previous playlist in separate thread
680 if ( restoreSession && AmarokConfig::savePlaylist() )
682 The::playlistModel()->restoreSession();
684 if( args->isSet( "engine" ) ) {
685 // we correct some common errors (case issues, missing -engine off the end)
686 QString engine = args->getOption( "engine" ).toLower();
687 if( engine.startsWith( "gstreamer" ) ) engine = "gst-engine";
688 if( !engine.endsWith( "engine" ) ) engine += "-engine";
690 AmarokConfig::setSoundSystem( engine );
692 Debug::stamp();
693 //create engine, show TrayIcon etc.
694 applySettings( true );
695 Debug::stamp();
696 // Start ScriptManager. Must be created _after_ MainWindow.
697 ScriptManager::instance();
698 Debug::stamp();
699 //notify loader application that we have started
700 std::cout << "STARTUP\n" << std::flush;
702 //do after applySettings(), or the OSD will flicker and other wierdness!
703 //do before restoreSession()!
704 EngineController::instance()->attach( this );
706 //set a default interface
707 engineStateChanged( Engine::Empty );
709 if ( AmarokConfig::resumePlayback() && restoreSession && !args->isSet( "stop" ) ) {
710 //restore session as long as the user didn't specify media to play etc.
711 //do this after applySettings() so OSD displays correctly
712 EngineController::instance()->restoreSession();
715 // Refetch covers every 80 days to comply with Amazon license
716 new RefreshImages();
718 CollectionDB *collDB = CollectionDB::instance();
720 // If database version is updated, the collection needs to be rescanned.
721 // Works also if the collection is empty for some other reason
722 // (e.g. deleted collection.db)
723 if ( CollectionDB::instance()->isEmpty() )
725 //connect( collDB, SIGNAL( databaseUpdateDone() ), collDB, SLOT( startScan() ) );
726 collDB->startScan();
728 else if ( AmarokConfig::monitorChanges() )
729 //connect( collDB, SIGNAL( databaseUpdateDone() ), collDB, SLOT( scanModifiedDirs() ) );
730 collDB->scanModifiedDirs();
733 handleCliArgs();
735 delete m_splash;
736 m_splash = 0;
739 bool Amarok::genericEventHandler( QWidget *recipient, QEvent *e )
741 //this is used as a generic event handler for widgets that want to handle
742 //typical events in an Amarok fashion
744 //to use it just pass the event eg:
746 // void Foo::barEvent( QBarEvent *e )
747 // {
748 // Amarok::genericEventHandler( this, e );
749 // }
751 switch( e->type() )
753 case QEvent::DragEnter:
754 #define e static_cast<QDropEvent*>(e)
755 e->setAccepted( KUrl::List::canDecode( e->mimeData() ) );
756 break;
758 case QEvent::Drop:
760 KUrl::List list = KUrl::List::fromMimeData( e->mimeData() );
761 if( !list.isEmpty() )
763 Q3PopupMenu popup;
764 //FIXME this isn't a good way to determine if there is a currentTrack, need playlist() function
765 const bool b = EngineController::engine()->loaded();
767 popup.insertItem( KIcon( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ),
768 Playlist::Append );
769 popup.insertItem( KIcon( Amarok::icon( "add_playlist" ) ), i18n( "Append && &Play" ),
770 Playlist::DirectPlay | Playlist::Append );
771 if( b )
772 popup.insertItem( KIcon( Amarok::icon( "fast_forward" ) ), i18n( "&Queue Track" ),
773 Playlist::Queue );
774 popup.addSeparator();
775 popup.insertItem( i18n( "&Cancel" ), 0 );
777 const int id = popup.exec( recipient->mapToGlobal( e->pos() ) );
779 if ( id > 0 )
780 The::playlistModel()->insertMedia( list, id );
782 else return false;
783 #undef e
785 break;
788 //this like every entry in the generic event handler is used by more than one widget
789 //please don't remove!
790 case QEvent::Wheel:
792 #define e static_cast<QWheelEvent*>(e)
794 //this behaviour happens for the systray
795 //to override one, override it in that class
797 switch( e->state() )
799 case Qt::ControlModifier:
801 const bool up = e->delta() > 0;
803 //if this seems strange to you, please bring it up on #amarok
804 //for discussion as we can't decide which way is best!
805 if( up ) EngineController::instance()->previous();
806 else EngineController::instance()->next();
807 break;
809 case Qt::ShiftModifier:
811 EngineController::instance()->seekRelative( ( e->delta() / 120 ) * 5000 ); //5 seconds for keyboard seeking.
812 break;
814 default:
815 EngineController::instance()->increaseVolume( e->delta() / Amarok::VOLUME_SENSITIVITY );
818 e->accept();
819 #undef e
821 break;
824 case QEvent::Close:
826 //KDE policy states we should hide to tray and not quit() when the
827 //close window button is pushed for the main widget
829 static_cast<QCloseEvent*>(e)->accept(); //if we don't do this the info box appears on quit()!
831 if( AmarokConfig::showTrayIcon() && !e->spontaneous() && !kapp->sessionSaving() )
833 KMessageBox::information( recipient,
834 i18n( "<qt>Closing the main-window will keep Amarok running in the System Tray. "
835 "Use <B>Quit</B> from the menu, or the Amarok tray-icon to exit the application.</qt>" ),
836 i18n( "Docking in System Tray" ), "hideOnCloseInfo" );
838 else pApp->quit();
840 break;
842 default:
843 return false;
846 return true;
850 void App::engineStateChanged( Engine::State state, Engine::State oldState )
852 Meta::TrackPtr track = EngineController::instance()->currentTrack();
853 //track is 0 if the engien state is Empty. we check that in the switch
854 switch( state )
856 case Engine::Empty:
857 mainWindow()->setCaption( "Amarok" );
858 TrackToolTip::instance()->clear();
859 Amarok::OSD::instance()->setImage( QImage( KIconLoader().iconPath( "amarok", -KIconLoader::SizeHuge ) ) );
860 break;
862 case Engine::Playing:
863 if ( oldState == Engine::Paused )
864 Amarok::OSD::instance()->OSDWidget::show( i18nc( "state, as in playing", "Play" ) );
865 if ( !track->prettyName().isEmpty() )
866 //TODO: write a utility function somewhere
867 mainWindow()->setCaption( i18n("Amarok - %1", /*bundle.veryNiceTitle()*/ track->prettyName() ) );
868 break;
870 case Engine::Paused:
871 Amarok::OSD::instance()->OSDWidget::show( i18n("Paused") );
872 break;
874 case Engine::Idle:
875 mainWindow()->setCaption( "Amarok" );
876 break;
878 default:
883 void App::engineNewMetaData( const MetaBundle &bundle, bool /*trackChanged*/ )
885 Amarok::OSD::instance()->show( bundle );
886 if ( !bundle.prettyTitle().isEmpty() )
887 mainWindow()->setCaption( i18n("Amarok - %1", bundle.veryNiceTitle() ) );
889 TrackToolTip::instance()->setTrack( bundle );
892 void App::engineTrackPositionChanged( long position, bool /*userSeek*/ )
894 TrackToolTip::instance()->setPos( position );
897 void App::engineVolumeChanged( int newVolume )
899 Amarok::OSD::instance()->OSDWidget::volChanged( newVolume );
902 void App::slotConfigEqualizer() //SLOT
904 EqualizerSetup::instance()->show();
905 EqualizerSetup::instance()->raise();
909 void App::slotConfigAmarok( const QByteArray& page )
911 DEBUG_THREAD_FUNC_INFO
913 Amarok2ConfigDialog* dialog = static_cast<Amarok2ConfigDialog*>( KConfigDialog::exists( "settings" ) );
915 if( !dialog )
917 //KConfigDialog didn't find an instance of this dialog, so lets create it :
918 dialog = new Amarok2ConfigDialog( mainWindow(), "settings", AmarokConfig::self() );
920 connect( dialog, SIGNAL(settingsChanged(const QString&)), SLOT(applySettings()) );
923 //FIXME it seems that if the dialog is on a different desktop it gets lost
924 // what do to? detect and move it?
926 // if ( page.isNull() )
927 // FIXME
928 // dialog->showPage( AmarokConfigDialog::s_currentPage );
929 // else
930 dialog->showPageByName( page );
932 dialog->show();
933 dialog->raise();
934 dialog->activateWindow();
937 void App::slotConfigShortcuts()
939 KShortcutsDialog::configure( Amarok::actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, mainWindow() );
942 void App::slotConfigToolBars()
944 KEditToolBar dialog( mainWindow()->actionCollection(), mainWindow() );
945 dialog.setResourceFile( mainWindow()->xmlFile() );
947 dialog.showButton( KEditToolBar::Apply, false );
949 // if( dialog.exec() )
950 // {
951 // mainWindow()->reloadXML();
952 // mainWindow()->createGUI();
953 // }
956 void App::setUseScores( bool use )
958 AmarokConfig::setUseScores( use );
959 emit useScores( use );
962 void App::setUseRatings( bool use )
964 AmarokConfig::setUseRatings( use );
965 emit useRatings( use );
968 void App::setMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic )
970 AmarokConfig::setShowMoodbar( show );
971 AmarokConfig::setMakeMoodier( moodier );
972 AmarokConfig::setAlterMood( alter );
973 AmarokConfig::setMoodsWithMusic( withMusic );
974 emit moodbarPrefs( show, moodier, alter, withMusic );
977 KIO::Job *App::trashFiles( const KUrl::List &files )
979 KIO::Job *job = KIO::trash( files );
980 Amarok::StatusBar::instance()->newProgressOperation( job ).setDescription( i18n("Moving files to trash") );
981 connect( job, SIGNAL( result( KJob* ) ), this, SLOT( slotTrashResult( KJob* ) ) );
982 return job;
985 void App::setRating( int n )
987 if( !AmarokConfig::useRatings() ) return;
989 n *= 2;
991 const Engine::State s = EngineController::instance()->engine()->state();
992 if( s == Engine::Playing || s == Engine::Paused || s == Engine::Idle )
994 Meta::TrackPtr track = EngineController::instance()->currentTrack();
995 track->setRating( n );
996 Amarok::OSD::instance()->OSDWidget::ratingChanged( track->rating() );
998 //PORT 2.0
999 // else if( MainWindow::self()->isReallyShown() && Playlist::instance()->qscrollview()->hasFocus() )
1000 // Playlist::instance()->setSelectedRatings( n );
1003 void App::slotTrashResult( KJob *job )
1005 if( job->error() )
1006 job->uiDelegate()->showErrorMessage();
1009 void App::quit()
1011 emit prepareToQuit();
1012 if( MediaBrowser::instance() && MediaBrowser::instance()->blockQuit() )
1014 // don't quit yet, as some media devices still have to finish transferring data
1015 QTimer::singleShot( 100, this, SLOT( quit() ) );
1016 return;
1018 KApplication::quit();
1021 namespace Amarok
1023 /// @see amarok.h
1025 QWidget *mainWindow()
1027 return pApp->mainWindow();
1030 KActionCollection *actionCollection()
1032 return pApp->mainWindow()->actionCollection();
1035 KConfigGroup config( const QString &group )
1037 //Slightly more useful config() that allows setting the group simultaneously
1038 // KGlobal::config()->setGroup( group );
1039 KConfigGroup configGroup = KGlobal::config()->group( group );
1040 return configGroup;
1043 bool invokeBrowser( const QString& url )
1045 //URL can be in whatever forms KUrl understands - ie most.
1046 const QString cmd = KShell::quoteArg(AmarokConfig::externalBrowser())
1047 + ' ' + KShell::quoteArg(KUrl( url ).url());
1048 return KRun::runCommand( cmd, 0L ) > 0;
1051 namespace ColorScheme
1053 QColor Base;
1054 QColor Text;
1055 QColor Background;
1056 QColor Foreground;
1057 QColor AltBase;
1060 OverrideCursor::OverrideCursor( Qt::CursorShape cursor )
1062 QApplication::setOverrideCursor( cursor == Qt::WaitCursor ?
1063 Qt::WaitCursor :
1064 Qt::BusyCursor );
1067 OverrideCursor::~OverrideCursor()
1069 QApplication::restoreOverrideCursor();
1072 QString saveLocation( const QString &directory )
1074 globalDirsMutex.lock();
1075 QString result = KGlobal::dirs()->saveLocation( "data", QString("amarok/") + directory, true );
1076 globalDirsMutex.unlock();
1077 return result;
1080 QString cleanPath( const QString &path )
1082 QString result = path;
1083 // german umlauts
1084 result.replace( QChar(0x00e4), "ae" ).replace( QChar(0x00c4), "Ae" );
1085 result.replace( QChar(0x00f6), "oe" ).replace( QChar(0x00d6), "Oe" );
1086 result.replace( QChar(0x00fc), "ue" ).replace( QChar(0x00dc), "Ue" );
1087 result.replace( QChar(0x00df), "ss" );
1089 // some strange accents
1090 result.replace( QChar(0x00e7), "c" ).replace( QChar(0x00c7), "C" );
1091 result.replace( QChar(0x00fd), "y" ).replace( QChar(0x00dd), "Y" );
1092 result.replace( QChar(0x00f1), "n" ).replace( QChar(0x00d1), "N" );
1094 // czech letters with carons
1095 result.replace( QChar(0x0161), "s" ).replace( QChar(0x0160), "S" );
1096 result.replace( QChar(0x010d), "c" ).replace( QChar(0x010c), "C" );
1097 result.replace( QChar(0x0159), "r" ).replace( QChar(0x0158), "R" );
1098 result.replace( QChar(0x017e), "z" ).replace( QChar(0x017d), "Z" );
1099 result.replace( QChar(0x0165), "t" ).replace( QChar(0x0164), "T" );
1100 result.replace( QChar(0x0148), "n" ).replace( QChar(0x0147), "N" );
1101 result.replace( QChar(0x010f), "d" ).replace( QChar(0x010e), "D" );
1103 // accented vowels
1104 QChar a[] = { 'a', 0xe0,0xe1,0xe2,0xe3,0xe5, 0 };
1105 QChar A[] = { 'A', 0xc0,0xc1,0xc2,0xc3,0xc5, 0 };
1106 QChar E[] = { 'e', 0xe8,0xe9,0xea,0xeb,0x11a, 0 };
1107 QChar e[] = { 'E', 0xc8,0xc9,0xca,0xcb,0x11b, 0 };
1108 QChar i[] = { 'i', 0xec,0xed,0xee,0xef, 0 };
1109 QChar I[] = { 'I', 0xcc,0xcd,0xce,0xcf, 0 };
1110 QChar o[] = { 'o', 0xf2,0xf3,0xf4,0xf5,0xf8, 0 };
1111 QChar O[] = { 'O', 0xd2,0xd3,0xd4,0xd5,0xd8, 0 };
1112 QChar u[] = { 'u', 0xf9,0xfa,0xfb,0x16e, 0 };
1113 QChar U[] = { 'U', 0xd9,0xda,0xdb,0x16f, 0 };
1114 QChar nul[] = { 0 };
1115 QChar *replacements[] = { a, A, e, E, i, I, o, O, u, U, nul };
1117 for( int i = 0; i < result.length(); i++ )
1119 QChar c = result[ i ];
1120 for( uint n = 0; replacements[n][0] != QChar(0); n++ )
1122 for( uint k=0; replacements[n][k] != QChar(0); k++ )
1124 if( replacements[n][k] == c )
1126 c = replacements[n][0];
1130 result[ i ] = c;
1132 return result;
1135 QString asciiPath( const QString &path )
1137 QString result = path;
1138 for( int i = 0; i < result.length(); i++ )
1140 QChar c = result[ i ];
1141 if( c > QChar(0x7f) || c == QChar(0) )
1143 c = '_';
1145 result[ i ] = c;
1147 return result;
1150 QString vfatPath( const QString &path )
1152 QString s = path;
1154 for( int i = 0; i < s.length(); i++ )
1156 QChar c = s[ i ];
1157 if( c < QChar(0x20)
1158 || c=='*' || c=='?' || c=='<' || c=='>'
1159 || c=='|' || c=='"' || c==':' || c=='/'
1160 || c=='\\' )
1161 c = '_';
1162 s[ i ] = c;
1165 uint len = s.length();
1166 if( len == 3 || (len > 3 && s[3] == '.') )
1168 QString l = s.left(3).toLower();
1169 if( l=="aux" || l=="con" || l=="nul" || l=="prn" )
1170 s = '_' + s;
1172 else if( len == 4 || (len > 4 && s[4] == '.') )
1174 QString l = s.left(3).toLower();
1175 QString d = s.mid(3,1);
1176 if( (l=="com" || l=="lpt") &&
1177 (d=="0" || d=="1" || d=="2" || d=="3" || d=="4" ||
1178 d=="5" || d=="6" || d=="7" || d=="8" || d=="9") )
1179 s = '_' + s;
1182 while( s.startsWith( '.' ) )
1183 s = s.mid(1);
1185 while( s.endsWith( '.' ) )
1186 s = s.left( s.length()-1 );
1188 s = s.left(255);
1189 len = s.length();
1190 if( s[len-1] == ' ' )
1191 s[len-1] = '_';
1193 return s;
1196 QString decapitateString( const QString &input, const QString &ref )
1198 QString t = ref.toUpper();
1199 int length = t.length();
1200 int commonLength = 0;
1201 while( length > 0 )
1203 if ( input.toUpper().startsWith( t ) )
1205 commonLength = t.length();
1206 t = ref.toUpper().left( t.length() + length/2 );
1207 length = length/2;
1209 else
1211 t = ref.toUpper().left( t.length() - length/2 );
1212 length = length/2;
1215 QString clean = input;
1216 if( t.endsWith( ' ' ) || !ref.at( t.length() ).isLetterOrNumber() ) // common part ends with a space or complete word
1217 clean = input.right( input.length() - commonLength ).trimmed();
1218 return clean;
1221 void setUseScores( bool use ) { App::instance()->setUseScores( use ); }
1222 void setUseRatings( bool use ) { App::instance()->setUseRatings( use ); }
1223 void setMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic )
1224 { App::instance()->setMoodbarPrefs( show, moodier, alter, withMusic ); }
1225 KIO::Job *trashFiles( const KUrl::List &files ) { return App::instance()->trashFiles( files ); }
1228 #include "app.moc"